···3535WHERE pkbr.is_duplicate = 0
3636GROUP BY pkbr.model
3737ORDER BY request_count DESC;
3838+3939+-- name: ListModelStatsFromRequests :many
4040+-- Per-model aggregate directly from potluck_requests.
4141+-- Covers all providers (including those without billing rows).
4242+-- since scopes the TPS window (48h); token counts are all-time.
4343+SELECT
4444+ model,
4545+ COUNT(*) AS request_count,
4646+ SUM(COALESCE(prompt_tokens, 0)) AS total_input_tokens,
4747+ SUM(COALESCE(completion_tokens, 0)) AS total_output_tokens,
4848+ AVG(
4949+ CASE
5050+ WHEN finished_at IS NOT NULL
5151+ AND finished_at > started_at
5252+ AND started_at >= ?
5353+ THEN CAST(COALESCE(completion_tokens, 0) AS REAL)
5454+ / (finished_at - started_at)
5555+ ELSE NULL
5656+ END
5757+ ) AS avg_tps
5858+FROM potluck_requests
5959+WHERE status = 'done'
6060+GROUP BY model
6161+ORDER BY request_count DESC;
+33-4
server/internal/api/web/models.go
···2323 refreshedAt = toInt64(ts)
2424 }
25252626- // Stats cover the last 48h.
2727- statsRows, _ := s.Q.ListModelStats(r.Context(), time.Now().Add(-48*time.Hour).Unix())
2828- statsMap := make(map[string]any, len(statsRows))
2929- for _, st := range statsRows {
2626+ // Stats cover the last 48h. Merge billing-row stats (pioneer) with
2727+ // potluck_requests stats (all providers). Request-based stats take
2828+ // precedence since they use the full prefixed model ID that matches
2929+ // the catalog.
3030+ since := time.Now().Add(-48 * time.Hour).Unix()
3131+ statsMap := make(map[string]any)
3232+3333+ // First: billing-row stats (legacy pioneer, unprefixed model names).
3434+ billingRows, _ := s.Q.ListModelStats(r.Context(), since)
3535+ for _, st := range billingRows {
3636+ var avgTps *float64
3737+ if st.AvgTps.Valid {
3838+ avgTps = &st.AvgTps.Float64
3939+ }
4040+ var totalIn, totalOut float64
4141+ if st.TotalInputTokens.Valid {
4242+ totalIn = st.TotalInputTokens.Float64
4343+ }
4444+ if st.TotalOutputTokens.Valid {
4545+ totalOut = st.TotalOutputTokens.Float64
4646+ }
4747+ statsMap[st.Model] = map[string]any{
4848+ "request_count": st.RequestCount,
4949+ "total_input_tokens": totalIn,
5050+ "total_output_tokens": totalOut,
5151+ "avg_tps": avgTps,
5252+ }
5353+ }
5454+5555+ // Second: request-based stats (all providers, prefixed model names).
5656+ // These override billing-row stats when keys match.
5757+ reqRows, _ := s.Q.ListModelStatsFromRequests(r.Context(), since)
5858+ for _, st := range reqRows {
3059 var avgTps *float64
3160 if st.AvgTps.Valid {
3261 avgTps = &st.AvgTps.Float64
+62
server/internal/store/prices.sql.go
···120120 return items, nil
121121}
122122123123+const listModelStatsFromRequests = `-- name: ListModelStatsFromRequests :many
124124+SELECT
125125+ model,
126126+ COUNT(*) AS request_count,
127127+ SUM(COALESCE(prompt_tokens, 0)) AS total_input_tokens,
128128+ SUM(COALESCE(completion_tokens, 0)) AS total_output_tokens,
129129+ AVG(
130130+ CASE
131131+ WHEN finished_at IS NOT NULL
132132+ AND finished_at > started_at
133133+ AND started_at >= ?
134134+ THEN CAST(COALESCE(completion_tokens, 0) AS REAL)
135135+ / (finished_at - started_at)
136136+ ELSE NULL
137137+ END
138138+ ) AS avg_tps
139139+FROM potluck_requests
140140+WHERE status = 'done'
141141+GROUP BY model
142142+ORDER BY request_count DESC
143143+`
144144+145145+type ListModelStatsFromRequestsRow struct {
146146+ Model string `json:"model"`
147147+ RequestCount int64 `json:"request_count"`
148148+ TotalInputTokens sql.NullFloat64 `json:"total_input_tokens"`
149149+ TotalOutputTokens sql.NullFloat64 `json:"total_output_tokens"`
150150+ AvgTps sql.NullFloat64 `json:"avg_tps"`
151151+}
152152+153153+// Per-model aggregate directly from potluck_requests.
154154+// Covers all providers (including those without billing rows).
155155+// since scopes the TPS window (48h); token counts are all-time.
156156+func (q *Queries) ListModelStatsFromRequests(ctx context.Context, startedAt int64) ([]ListModelStatsFromRequestsRow, error) {
157157+ rows, err := q.db.QueryContext(ctx, listModelStatsFromRequests, startedAt)
158158+ if err != nil {
159159+ return nil, err
160160+ }
161161+ defer rows.Close()
162162+ items := []ListModelStatsFromRequestsRow{}
163163+ for rows.Next() {
164164+ var i ListModelStatsFromRequestsRow
165165+ if err := rows.Scan(
166166+ &i.Model,
167167+ &i.RequestCount,
168168+ &i.TotalInputTokens,
169169+ &i.TotalOutputTokens,
170170+ &i.AvgTps,
171171+ ); err != nil {
172172+ return nil, err
173173+ }
174174+ items = append(items, i)
175175+ }
176176+ if err := rows.Close(); err != nil {
177177+ return nil, err
178178+ }
179179+ if err := rows.Err(); err != nil {
180180+ return nil, err
181181+ }
182182+ return items, nil
183183+}
184184+123185const upsertModelPrice = `-- name: UpsertModelPrice :exec
124186INSERT INTO model_prices (model, input_micros_per_1k, output_micros_per_1k, updated_at)
125187VALUES (?, ?, ?, ?)
+4
server/internal/store/querier.go
···110110 // Per-model aggregate from billing rows + potluck_requests.
111111 // since scopes the TPS window (48h); cost/token counts are all-time.
112112 ListModelStats(ctx context.Context, startedAt int64) ([]ListModelStatsRow, error)
113113+ // Per-model aggregate directly from potluck_requests.
114114+ // Covers all providers (including those without billing rows).
115115+ // since scopes the TPS window (48h); token counts are all-time.
116116+ ListModelStatsFromRequests(ctx context.Context, startedAt int64) ([]ListModelStatsFromRequestsRow, error)
113117 // All users with their pool key stats (if any).
114118 // Users without keys appear with zero contributions.
115119 // private_reservation_micros = sum(max_micros - shared_micros) for active keys.