usage_service.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "time"
  7. dbent "github.com/Wei-Shaw/sub2api/ent"
  8. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  9. "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
  10. "github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
  11. )
  12. var (
  13. ErrUsageLogNotFound = infraerrors.NotFound("USAGE_LOG_NOT_FOUND", "usage log not found")
  14. )
  15. // CreateUsageLogRequest 创建使用日志请求
  16. type CreateUsageLogRequest struct {
  17. UserID int64 `json:"user_id"`
  18. APIKeyID int64 `json:"api_key_id"`
  19. AccountID int64 `json:"account_id"`
  20. RequestID string `json:"request_id"`
  21. Model string `json:"model"`
  22. InputTokens int `json:"input_tokens"`
  23. OutputTokens int `json:"output_tokens"`
  24. CacheCreationTokens int `json:"cache_creation_tokens"`
  25. CacheReadTokens int `json:"cache_read_tokens"`
  26. CacheCreation5mTokens int `json:"cache_creation_5m_tokens"`
  27. CacheCreation1hTokens int `json:"cache_creation_1h_tokens"`
  28. InputCost float64 `json:"input_cost"`
  29. OutputCost float64 `json:"output_cost"`
  30. CacheCreationCost float64 `json:"cache_creation_cost"`
  31. CacheReadCost float64 `json:"cache_read_cost"`
  32. TotalCost float64 `json:"total_cost"`
  33. ActualCost float64 `json:"actual_cost"`
  34. RateMultiplier float64 `json:"rate_multiplier"`
  35. Stream bool `json:"stream"`
  36. DurationMs *int `json:"duration_ms"`
  37. }
  38. // UsageStats 使用统计
  39. type UsageStats struct {
  40. TotalRequests int64 `json:"total_requests"`
  41. TotalInputTokens int64 `json:"total_input_tokens"`
  42. TotalOutputTokens int64 `json:"total_output_tokens"`
  43. TotalCacheTokens int64 `json:"total_cache_tokens"`
  44. TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
  45. TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
  46. TotalTokens int64 `json:"total_tokens"`
  47. TotalCost float64 `json:"total_cost"`
  48. TotalActualCost float64 `json:"total_actual_cost"`
  49. AverageDurationMs float64 `json:"average_duration_ms"`
  50. }
  51. // UsageService 使用统计服务
  52. type UsageService struct {
  53. usageRepo UsageLogRepository
  54. userRepo UserRepository
  55. entClient *dbent.Client
  56. authCacheInvalidator APIKeyAuthCacheInvalidator
  57. }
  58. // NewUsageService 创建使用统计服务实例
  59. func NewUsageService(usageRepo UsageLogRepository, userRepo UserRepository, entClient *dbent.Client, authCacheInvalidator APIKeyAuthCacheInvalidator) *UsageService {
  60. return &UsageService{
  61. usageRepo: usageRepo,
  62. userRepo: userRepo,
  63. entClient: entClient,
  64. authCacheInvalidator: authCacheInvalidator,
  65. }
  66. }
  67. // Create 创建使用日志
  68. func (s *UsageService) Create(ctx context.Context, req CreateUsageLogRequest) (*UsageLog, error) {
  69. // 使用数据库事务保证「使用日志插入」与「扣费」的原子性,避免重复扣费或漏扣风险。
  70. tx, err := s.entClient.Tx(ctx)
  71. if err != nil && !errors.Is(err, dbent.ErrTxStarted) {
  72. return nil, fmt.Errorf("begin transaction: %w", err)
  73. }
  74. txCtx := ctx
  75. if err == nil {
  76. defer func() { _ = tx.Rollback() }()
  77. txCtx = dbent.NewTxContext(ctx, tx)
  78. }
  79. // 验证用户存在
  80. _, err = s.userRepo.GetByID(txCtx, req.UserID)
  81. if err != nil {
  82. return nil, fmt.Errorf("get user: %w", err)
  83. }
  84. // 创建使用日志
  85. usageLog := &UsageLog{
  86. UserID: req.UserID,
  87. APIKeyID: req.APIKeyID,
  88. AccountID: req.AccountID,
  89. RequestID: req.RequestID,
  90. Model: req.Model,
  91. InputTokens: req.InputTokens,
  92. OutputTokens: req.OutputTokens,
  93. CacheCreationTokens: req.CacheCreationTokens,
  94. CacheReadTokens: req.CacheReadTokens,
  95. CacheCreation5mTokens: req.CacheCreation5mTokens,
  96. CacheCreation1hTokens: req.CacheCreation1hTokens,
  97. InputCost: req.InputCost,
  98. OutputCost: req.OutputCost,
  99. CacheCreationCost: req.CacheCreationCost,
  100. CacheReadCost: req.CacheReadCost,
  101. TotalCost: req.TotalCost,
  102. ActualCost: req.ActualCost,
  103. RateMultiplier: req.RateMultiplier,
  104. Stream: req.Stream,
  105. DurationMs: req.DurationMs,
  106. }
  107. inserted, err := s.usageRepo.Create(txCtx, usageLog)
  108. if err != nil {
  109. return nil, fmt.Errorf("create usage log: %w", err)
  110. }
  111. // 扣除用户余额
  112. balanceUpdated := false
  113. if inserted && req.ActualCost > 0 {
  114. if err := s.userRepo.UpdateBalance(txCtx, req.UserID, -req.ActualCost); err != nil {
  115. return nil, fmt.Errorf("update user balance: %w", err)
  116. }
  117. balanceUpdated = true
  118. }
  119. if tx != nil {
  120. if err := tx.Commit(); err != nil {
  121. return nil, fmt.Errorf("commit transaction: %w", err)
  122. }
  123. }
  124. s.invalidateUsageCaches(ctx, req.UserID, balanceUpdated)
  125. return usageLog, nil
  126. }
  127. func (s *UsageService) invalidateUsageCaches(ctx context.Context, userID int64, balanceUpdated bool) {
  128. if !balanceUpdated || s.authCacheInvalidator == nil {
  129. return
  130. }
  131. s.authCacheInvalidator.InvalidateAuthCacheByUserID(ctx, userID)
  132. }
  133. // GetByID 根据ID获取使用日志
  134. func (s *UsageService) GetByID(ctx context.Context, id int64) (*UsageLog, error) {
  135. log, err := s.usageRepo.GetByID(ctx, id)
  136. if err != nil {
  137. return nil, fmt.Errorf("get usage log: %w", err)
  138. }
  139. return log, nil
  140. }
  141. // ListByUser 获取用户的使用日志列表
  142. func (s *UsageService) ListByUser(ctx context.Context, userID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
  143. logs, pagination, err := s.usageRepo.ListByUser(ctx, userID, params)
  144. if err != nil {
  145. return nil, nil, fmt.Errorf("list usage logs: %w", err)
  146. }
  147. return logs, pagination, nil
  148. }
  149. // ListByAPIKey 获取API Key的使用日志列表
  150. func (s *UsageService) ListByAPIKey(ctx context.Context, apiKeyID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
  151. logs, pagination, err := s.usageRepo.ListByAPIKey(ctx, apiKeyID, params)
  152. if err != nil {
  153. return nil, nil, fmt.Errorf("list usage logs: %w", err)
  154. }
  155. return logs, pagination, nil
  156. }
  157. // ListByAccount 获取账号的使用日志列表
  158. func (s *UsageService) ListByAccount(ctx context.Context, accountID int64, params pagination.PaginationParams) ([]UsageLog, *pagination.PaginationResult, error) {
  159. logs, pagination, err := s.usageRepo.ListByAccount(ctx, accountID, params)
  160. if err != nil {
  161. return nil, nil, fmt.Errorf("list usage logs: %w", err)
  162. }
  163. return logs, pagination, nil
  164. }
  165. // GetStatsByUser 获取用户的使用统计
  166. func (s *UsageService) GetStatsByUser(ctx context.Context, userID int64, startTime, endTime time.Time) (*UsageStats, error) {
  167. stats, err := s.usageRepo.GetUserStatsAggregated(ctx, userID, startTime, endTime)
  168. if err != nil {
  169. return nil, fmt.Errorf("get user stats: %w", err)
  170. }
  171. return &UsageStats{
  172. TotalRequests: stats.TotalRequests,
  173. TotalInputTokens: stats.TotalInputTokens,
  174. TotalOutputTokens: stats.TotalOutputTokens,
  175. TotalCacheTokens: stats.TotalCacheTokens,
  176. TotalCacheCreationTokens: stats.TotalCacheCreationTokens,
  177. TotalCacheReadTokens: stats.TotalCacheReadTokens,
  178. TotalTokens: stats.TotalTokens,
  179. TotalCost: stats.TotalCost,
  180. TotalActualCost: stats.TotalActualCost,
  181. AverageDurationMs: stats.AverageDurationMs,
  182. }, nil
  183. }
  184. // GetStatsByAPIKey 获取API Key的使用统计
  185. func (s *UsageService) GetStatsByAPIKey(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) (*UsageStats, error) {
  186. stats, err := s.usageRepo.GetAPIKeyStatsAggregated(ctx, apiKeyID, startTime, endTime)
  187. if err != nil {
  188. return nil, fmt.Errorf("get api key stats: %w", err)
  189. }
  190. return &UsageStats{
  191. TotalRequests: stats.TotalRequests,
  192. TotalInputTokens: stats.TotalInputTokens,
  193. TotalOutputTokens: stats.TotalOutputTokens,
  194. TotalCacheTokens: stats.TotalCacheTokens,
  195. TotalCacheCreationTokens: stats.TotalCacheCreationTokens,
  196. TotalCacheReadTokens: stats.TotalCacheReadTokens,
  197. TotalTokens: stats.TotalTokens,
  198. TotalCost: stats.TotalCost,
  199. TotalActualCost: stats.TotalActualCost,
  200. AverageDurationMs: stats.AverageDurationMs,
  201. }, nil
  202. }
  203. // GetStatsByAccount 获取账号的使用统计
  204. func (s *UsageService) GetStatsByAccount(ctx context.Context, accountID int64, startTime, endTime time.Time) (*UsageStats, error) {
  205. stats, err := s.usageRepo.GetAccountStatsAggregated(ctx, accountID, startTime, endTime)
  206. if err != nil {
  207. return nil, fmt.Errorf("get account stats: %w", err)
  208. }
  209. return &UsageStats{
  210. TotalRequests: stats.TotalRequests,
  211. TotalInputTokens: stats.TotalInputTokens,
  212. TotalOutputTokens: stats.TotalOutputTokens,
  213. TotalCacheTokens: stats.TotalCacheTokens,
  214. TotalCacheCreationTokens: stats.TotalCacheCreationTokens,
  215. TotalCacheReadTokens: stats.TotalCacheReadTokens,
  216. TotalTokens: stats.TotalTokens,
  217. TotalCost: stats.TotalCost,
  218. TotalActualCost: stats.TotalActualCost,
  219. AverageDurationMs: stats.AverageDurationMs,
  220. }, nil
  221. }
  222. // GetStatsByModel 获取模型的使用统计
  223. func (s *UsageService) GetStatsByModel(ctx context.Context, modelName string, startTime, endTime time.Time) (*UsageStats, error) {
  224. stats, err := s.usageRepo.GetModelStatsAggregated(ctx, modelName, startTime, endTime)
  225. if err != nil {
  226. return nil, fmt.Errorf("get model stats: %w", err)
  227. }
  228. return &UsageStats{
  229. TotalRequests: stats.TotalRequests,
  230. TotalInputTokens: stats.TotalInputTokens,
  231. TotalOutputTokens: stats.TotalOutputTokens,
  232. TotalCacheTokens: stats.TotalCacheTokens,
  233. TotalCacheCreationTokens: stats.TotalCacheCreationTokens,
  234. TotalCacheReadTokens: stats.TotalCacheReadTokens,
  235. TotalTokens: stats.TotalTokens,
  236. TotalCost: stats.TotalCost,
  237. TotalActualCost: stats.TotalActualCost,
  238. AverageDurationMs: stats.AverageDurationMs,
  239. }, nil
  240. }
  241. // GetDailyStats 获取每日使用统计(最近N天)
  242. func (s *UsageService) GetDailyStats(ctx context.Context, userID int64, days int) ([]map[string]any, error) {
  243. endTime := time.Now()
  244. startTime := endTime.AddDate(0, 0, -days)
  245. stats, err := s.usageRepo.GetDailyStatsAggregated(ctx, userID, startTime, endTime)
  246. if err != nil {
  247. return nil, fmt.Errorf("get daily stats: %w", err)
  248. }
  249. return stats, nil
  250. }
  251. // Delete 删除使用日志(管理员功能,谨慎使用)
  252. func (s *UsageService) Delete(ctx context.Context, id int64) error {
  253. if err := s.usageRepo.Delete(ctx, id); err != nil {
  254. return fmt.Errorf("delete usage log: %w", err)
  255. }
  256. return nil
  257. }
  258. // GetUserDashboardStats returns per-user dashboard summary stats.
  259. func (s *UsageService) GetUserDashboardStats(ctx context.Context, userID int64) (*usagestats.UserDashboardStats, error) {
  260. stats, err := s.usageRepo.GetUserDashboardStats(ctx, userID)
  261. if err != nil {
  262. return nil, fmt.Errorf("get user dashboard stats: %w", err)
  263. }
  264. return stats, nil
  265. }
  266. // GetAPIKeyDashboardStats returns dashboard summary stats filtered by API Key.
  267. func (s *UsageService) GetAPIKeyDashboardStats(ctx context.Context, apiKeyID int64) (*usagestats.UserDashboardStats, error) {
  268. stats, err := s.usageRepo.GetAPIKeyDashboardStats(ctx, apiKeyID)
  269. if err != nil {
  270. return nil, fmt.Errorf("get api key dashboard stats: %w", err)
  271. }
  272. return stats, nil
  273. }
  274. // GetUserUsageTrendByUserID returns per-user usage trend.
  275. func (s *UsageService) GetUserUsageTrendByUserID(ctx context.Context, userID int64, startTime, endTime time.Time, granularity string) ([]usagestats.TrendDataPoint, error) {
  276. trend, err := s.usageRepo.GetUserUsageTrendByUserID(ctx, userID, startTime, endTime, granularity)
  277. if err != nil {
  278. return nil, fmt.Errorf("get user usage trend: %w", err)
  279. }
  280. return trend, nil
  281. }
  282. // GetUsageTrendWithFilters returns trend data using the shared usage filter shape.
  283. func (s *UsageService) GetUsageTrendWithFilters(ctx context.Context, startTime, endTime time.Time, granularity string, filters usagestats.UsageLogFilters) ([]usagestats.TrendDataPoint, error) {
  284. type usageTrendWithFiltersRepo interface {
  285. GetUsageTrendWithUsageFilters(ctx context.Context, startTime, endTime time.Time, granularity string, filters usagestats.UsageLogFilters) ([]usagestats.TrendDataPoint, error)
  286. }
  287. if filterRepo, ok := s.usageRepo.(usageTrendWithFiltersRepo); ok {
  288. trend, err := filterRepo.GetUsageTrendWithUsageFilters(ctx, startTime, endTime, granularity, filters)
  289. if err != nil {
  290. return nil, fmt.Errorf("get usage trend with filters: %w", err)
  291. }
  292. return trend, nil
  293. }
  294. trend, err := s.usageRepo.GetUsageTrendWithFilters(ctx, startTime, endTime, granularity, filters.UserID, filters.APIKeyID, filters.AccountID, filters.GroupID, filters.Model, filters.RequestType, filters.Stream, filters.BillingType)
  295. if err != nil {
  296. return nil, fmt.Errorf("get usage trend with filters: %w", err)
  297. }
  298. return trend, nil
  299. }
  300. // GetUserModelStats returns per-user model usage stats.
  301. func (s *UsageService) GetUserModelStats(ctx context.Context, userID int64, startTime, endTime time.Time) ([]usagestats.ModelStat, error) {
  302. stats, err := s.usageRepo.GetUserModelStats(ctx, userID, startTime, endTime)
  303. if err != nil {
  304. return nil, fmt.Errorf("get user model stats: %w", err)
  305. }
  306. return stats, nil
  307. }
  308. // GetModelStatsWithFiltersBySource returns model stats using the shared usage filter shape.
  309. func (s *UsageService) GetModelStatsWithFiltersBySource(ctx context.Context, startTime, endTime time.Time, filters usagestats.UsageLogFilters, modelSource string) ([]usagestats.ModelStat, error) {
  310. normalizedSource := usagestats.NormalizeModelSource(modelSource)
  311. type modelStatsWithUsageFiltersRepo interface {
  312. GetModelStatsWithUsageFiltersBySource(ctx context.Context, startTime, endTime time.Time, filters usagestats.UsageLogFilters, source string) ([]usagestats.ModelStat, error)
  313. }
  314. if filterRepo, ok := s.usageRepo.(modelStatsWithUsageFiltersRepo); ok {
  315. stats, err := filterRepo.GetModelStatsWithUsageFiltersBySource(ctx, startTime, endTime, filters, normalizedSource)
  316. if err != nil {
  317. return nil, fmt.Errorf("get model stats with filters by source: %w", err)
  318. }
  319. return stats, nil
  320. }
  321. type modelStatsBySourceRepo interface {
  322. GetModelStatsWithFiltersBySource(ctx context.Context, startTime, endTime time.Time, userID, apiKeyID, accountID, groupID int64, requestType *int16, stream *bool, billingType *int8, source string) ([]usagestats.ModelStat, error)
  323. }
  324. if sourceRepo, ok := s.usageRepo.(modelStatsBySourceRepo); ok {
  325. stats, err := sourceRepo.GetModelStatsWithFiltersBySource(ctx, startTime, endTime, filters.UserID, filters.APIKeyID, filters.AccountID, filters.GroupID, filters.RequestType, filters.Stream, filters.BillingType, normalizedSource)
  326. if err != nil {
  327. return nil, fmt.Errorf("get model stats with filters by source: %w", err)
  328. }
  329. return stats, nil
  330. }
  331. stats, err := s.usageRepo.GetModelStatsWithFilters(ctx, startTime, endTime, filters.UserID, filters.APIKeyID, filters.AccountID, filters.GroupID, filters.RequestType, filters.Stream, filters.BillingType)
  332. if err != nil {
  333. return nil, fmt.Errorf("get model stats with filters: %w", err)
  334. }
  335. return stats, nil
  336. }
  337. // GetGroupStatsWithFilters returns group stats using the shared usage filter shape.
  338. func (s *UsageService) GetGroupStatsWithFilters(ctx context.Context, startTime, endTime time.Time, filters usagestats.UsageLogFilters) ([]usagestats.GroupStat, error) {
  339. type groupStatsWithUsageFiltersRepo interface {
  340. GetGroupStatsWithUsageFilters(ctx context.Context, startTime, endTime time.Time, filters usagestats.UsageLogFilters) ([]usagestats.GroupStat, error)
  341. }
  342. if filterRepo, ok := s.usageRepo.(groupStatsWithUsageFiltersRepo); ok {
  343. stats, err := filterRepo.GetGroupStatsWithUsageFilters(ctx, startTime, endTime, filters)
  344. if err != nil {
  345. return nil, fmt.Errorf("get group stats with filters: %w", err)
  346. }
  347. return stats, nil
  348. }
  349. stats, err := s.usageRepo.GetGroupStatsWithFilters(ctx, startTime, endTime, filters.UserID, filters.APIKeyID, filters.AccountID, filters.GroupID, filters.RequestType, filters.Stream, filters.BillingType)
  350. if err != nil {
  351. return nil, fmt.Errorf("get group stats with filters: %w", err)
  352. }
  353. return stats, nil
  354. }
  355. // GetAPIKeyModelStats returns per-model usage stats for a specific API Key.
  356. func (s *UsageService) GetAPIKeyModelStats(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) ([]usagestats.ModelStat, error) {
  357. stats, err := s.usageRepo.GetModelStatsWithFilters(ctx, startTime, endTime, 0, apiKeyID, 0, 0, nil, nil, nil)
  358. if err != nil {
  359. return nil, fmt.Errorf("get api key model stats: %w", err)
  360. }
  361. return stats, nil
  362. }
  363. // GetAPIKeyDailyUsage returns daily usage stats for a user's API key.
  364. func (s *UsageService) GetAPIKeyDailyUsage(ctx context.Context, userID, apiKeyID int64, startTime, endTime time.Time) ([]usagestats.APIKeyDailyUsagePoint, error) {
  365. trend, err := s.usageRepo.GetUsageTrendWithFilters(ctx, startTime, endTime, "day", userID, apiKeyID, 0, 0, "", nil, nil, nil)
  366. if err != nil {
  367. return nil, fmt.Errorf("get api key daily usage: %w", err)
  368. }
  369. points := make([]usagestats.APIKeyDailyUsagePoint, 0, len(trend))
  370. for _, row := range trend {
  371. points = append(points, usagestats.APIKeyDailyUsagePoint{
  372. Date: row.Date,
  373. Requests: row.Requests,
  374. InputTokens: row.InputTokens,
  375. OutputTokens: row.OutputTokens,
  376. CacheReadTokens: row.CacheReadTokens,
  377. CacheWriteTokens: row.CacheCreationTokens,
  378. TotalTokens: row.TotalTokens,
  379. Cost: row.Cost,
  380. ActualCost: row.ActualCost,
  381. })
  382. }
  383. return points, nil
  384. }
  385. // GetBatchAPIKeyUsageStats returns today/total actual_cost for given api keys.
  386. func (s *UsageService) GetBatchAPIKeyUsageStats(ctx context.Context, apiKeyIDs []int64, startTime, endTime time.Time) (map[int64]*usagestats.BatchAPIKeyUsageStats, error) {
  387. stats, err := s.usageRepo.GetBatchAPIKeyUsageStats(ctx, apiKeyIDs, startTime, endTime)
  388. if err != nil {
  389. return nil, fmt.Errorf("get batch api key usage stats: %w", err)
  390. }
  391. return stats, nil
  392. }
  393. // ListWithFilters lists usage logs with admin filters.
  394. func (s *UsageService) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]UsageLog, *pagination.PaginationResult, error) {
  395. logs, result, err := s.usageRepo.ListWithFilters(ctx, params, filters)
  396. if err != nil {
  397. return nil, nil, fmt.Errorf("list usage logs with filters: %w", err)
  398. }
  399. return logs, result, nil
  400. }
  401. // GetGlobalStats returns global usage stats for a time range.
  402. func (s *UsageService) GetGlobalStats(ctx context.Context, startTime, endTime time.Time) (*usagestats.UsageStats, error) {
  403. stats, err := s.usageRepo.GetGlobalStats(ctx, startTime, endTime)
  404. if err != nil {
  405. return nil, fmt.Errorf("get global usage stats: %w", err)
  406. }
  407. return stats, nil
  408. }
  409. // GetStatsWithFilters returns usage stats with optional filters.
  410. func (s *UsageService) GetStatsWithFilters(ctx context.Context, filters usagestats.UsageLogFilters) (*usagestats.UsageStats, error) {
  411. stats, err := s.usageRepo.GetStatsWithFilters(ctx, filters)
  412. if err != nil {
  413. return nil, fmt.Errorf("get usage stats with filters: %w", err)
  414. }
  415. return stats, nil
  416. }