group_detail_stats.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "time"
  8. )
  9. func (r *usageLogRepository) GetGroupDetailStats(ctx context.Context, id int64, from, to *time.Time) (*service.GroupDetailStats, error) {
  10. out := &service.GroupDetailStats{GroupID: id, From: from, To: to, GeneratedAt: time.Now().UTC()}
  11. // Independent aggregates avoid multiplying usage by the number of keys or
  12. // accounts. Historical charges belong to usage_logs.group_id even when a
  13. // key is later moved or deleted. Account cost uses each log's saved pricing.
  14. query := `SELECT g.name,
  15. (SELECT COUNT(*) FROM api_keys k WHERE k.group_id=g.id AND k.deleted_at IS NULL),
  16. (SELECT COUNT(*) FROM api_keys k WHERE k.group_id=g.id AND k.deleted_at IS NULL AND k.status='active' AND (k.expires_at IS NULL OR k.expires_at>NOW())),
  17. (SELECT COUNT(DISTINCT ag.account_id) FROM account_groups ag JOIN accounts a ON a.id=ag.account_id WHERE ag.group_id=g.id AND a.deleted_at IS NULL),
  18. u.requests,u.tokens,u.cost,u.actual,u.account_cost,u.balance,u.subscription,u.zero_charge,u.duration
  19. FROM groups g CROSS JOIN LATERAL (
  20. SELECT COUNT(*) AS requests,
  21. COALESCE(SUM(input_tokens::bigint+output_tokens+cache_creation_tokens+cache_read_tokens),0) AS tokens,
  22. COALESCE(SUM(total_cost),0) AS cost,
  23. COALESCE(SUM(actual_cost),0) AS actual,
  24. COALESCE(SUM(COALESCE(account_stats_cost,total_cost)*COALESCE(account_rate_multiplier,1)),0) AS account_cost,
  25. COALESCE(SUM(actual_cost) FILTER(WHERE billing_type=0),0) AS balance,
  26. COALESCE(SUM(actual_cost) FILTER(WHERE billing_type=1),0) AS subscription,
  27. COUNT(*) FILTER(WHERE actual_cost=0) AS zero_charge,
  28. COALESCE(AVG(duration_ms),0) AS duration
  29. FROM usage_logs WHERE group_id=g.id AND ($2::timestamptz IS NULL OR created_at >= $2) AND ($3::timestamptz IS NULL OR created_at < $3)
  30. ) u WHERE g.id=$1 AND g.deleted_at IS NULL`
  31. err := scanSingleRow(ctx, r.sql, query, []any{id, from, to}, &out.GroupName, &out.TotalAPIKeys, &out.ActiveAPIKeys, &out.TotalAccounts, &out.TotalRequests, &out.TotalTokens, &out.TotalCost, &out.TotalActualCost, &out.TotalAccountCost, &out.BalanceCost, &out.SubscriptionCost, &out.ZeroChargeRequests, &out.AverageDurationMS)
  32. if errors.Is(err, sql.ErrNoRows) {
  33. return nil, service.ErrGroupNotFound
  34. }
  35. return out, err
  36. }