Skip to content

fix: 修复首页模型排行榜排序问题#1127

Merged
ding113 merged 1 commit into
devfrom
fix/dashboard-model-ranking-order
Apr 28, 2026
Merged

fix: 修复首页模型排行榜排序问题#1127
ding113 merged 1 commit into
devfrom
fix/dashboard-model-ranking-order

Conversation

@hank9999

@hank9999 hank9999 commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix the model leaderboard sort order to rank by total cost (descending) instead of request count, aligning it with the user and provider leaderboards.

Problem

The model leaderboard query (findModelLeaderboardWithTimezone) was sorting by count(*) (request count) while all other leaderboard scopes (user at L367/L407, provider at L699) sort by sum(cost_usd). This caused the "Cost Leaderboard" display to show models ordered by volume rather than cost, making expensive low-volume models appear below cheap high-volume ones.

Solution

Switch the ORDER BY clause from:

ORDER BY count(*) DESC

to:

ORDER BY sum(cost_usd) DESC, count(*) DESC

This matches the provider model sub-rows pattern (L750) and is consistent with all other leaderboard scopes.

Changes

  • src/repository/leaderboard.ts: Change model leaderboard orderBy to sum(cost_usd) DESC with count(*) as tiebreaker
  • tests/unit/repository/leaderboard-provider-metrics.test.ts: Add test verifying cost-based ordering with an expensive-low-volume model ranking above a cheap-high-volume model

Testing

  • Unit test added: verifies models sort by total cost descending with request count as tiebreaker

Description enhanced by Claude AI

Greptile Summary

This PR fixes the homepage model leaderboard to sort by total cost (descending) with request count as a tiebreaker, replacing the previous sort by request count alone. It bundles several related improvements: explicit locale propagation to getTranslations() across all RSC pages (fixing potential locale-mismatch in server components), a LanguageSwitcher refresh fix that triggers router.refresh() after locale navigation, per-request billing support for image APIs without token usage, and extraction of the Langfuse trace helper into a shared module with error-path coverage.

Confidence Score: 4/5

Safe to merge; all changes are well-tested and the single finding is a minor ORDER BY NULL-handling inconsistency.

Only P2 findings present. The core leaderboard sort fix is correct and covered by a new unit test. The i18n, language-switcher, billing, and Langfuse changes all have matching tests. One minor inconsistency exists between COALESCE in SELECT vs bare sum() in ORDER BY, but practical impact is negligible.

src/repository/leaderboard.ts — minor COALESCE inconsistency between SELECT and ORDER BY for null costUsd rows.

Important Files Changed

Filename Overview
src/repository/leaderboard.ts Core fix: changed model leaderboard sort from request count to cost (sum costUsd) with count as tiebreaker; minor NULL-vs-COALESCE inconsistency between SELECT and ORDER BY.
src/app/v1/_lib/proxy/response-handler.ts Added per-request billing support for image/no-token-usage APIs; extracted emitLangfuseTrace to shared module; billable usage resolution now handles input_cost_per_request.
src/app/v1/_lib/proxy/error-handler.ts Added Langfuse trace emission for both rate-limit and generic error paths; uses shared emitProxyLangfuseTrace helper.
src/components/ui/language-switcher.tsx Added router.refresh() after locale change with dual-layer persistence (module-level var + sessionStorage) to correctly refresh RSC tree and unblock the disabled trigger button.
src/lib/langfuse/emit-proxy-trace.ts New module extracting the fire-and-forget Langfuse trace helper previously inlined in response-handler.ts; no issues.
tests/integration/billing-model-source.test.ts Comprehensive tests for per-request billing (image edit endpoints): multipliers, zero-cost guard, pricing DB failure, fake-200 error detection, session usage payload.
tests/unit/repository/leaderboard-provider-metrics.test.ts Added test suite verifying cost-first sort order with request count as tiebreaker; tests ORDER BY argument structure and result ordering.
tests/unit/i18n/locale-server-translations.test.ts New regression test ensuring all route pages and server chrome use explicit locale form of getTranslations rather than the implicit context-based form.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Model Leaderboard Query] --> B[GROUP BY modelField]
    B --> C{ORDER BY}
    C -->|Before PR| D["count(*) DESC\n(request count)"]
    C -->|After PR| E["sum(costUsd) DESC\nthen count(*) DESC"]
    E --> F[Return sorted rankings]
    D --> F
    F --> G[Filter null/empty models]
    G --> H[Map to ModelLeaderboardEntry]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/repository/leaderboard.ts
Line: 1156

Comment:
**NULL handling inconsistency in ORDER BY vs SELECT**

The SELECT clause uses `COALESCE(sum(${usageLedger.costUsd}), 0)` (so `totalCost` is always a number), but the ORDER BY uses `sum(${usageLedger.costUsd})` without COALESCE. In PostgreSQL, a group where all `costUsd` values are NULL produces `SUM = NULL`, which sorts **last** under `DESC` — below models with an explicit `$0.00` cost. Models with no billing data at all would rank below models billed at exactly zero, which is inconsistent with the `totalCost` value returned to the caller.

```suggestion
    .orderBy(desc(sql`COALESCE(sum(${usageLedger.costUsd}), 0)`), desc(sql`count(*)`));
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(dashboard): order model leaderboard ..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

The model rankings query was sorting by request count, which
contradicted the "Cost Leaderboard" framing and diverged from the
user and provider scopes that both order by sum(cost_usd). Switch
the ORDER BY to sum(cost_usd) DESC with request count as tiebreaker,
matching the provider model sub-rows pattern.
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

此PR通过将多个页面和组件更新为接收动态locale参数并使用getTranslations({ locale, namespace: "..." })形式调用来完善国际化处理;在代理错误处理和响应处理中添加Langfuse跟踪功能;改进了缺少usage metrics情况下的计费逻辑;并将排行榜排序优先级从请求数改为成本。

Changes

Cohort / File(s) Summary
Dashboard和Settings头部/布局组件
src/app/[locale]/dashboard/_components/dashboard-header.tsx, src/app/[locale]/dashboard/layout.tsx, src/app/[locale]/settings/layout.tsx, src/app/[locale]/usage-doc/layout.tsx
更新为接收并向DashboardHeader传递locale参数,确保导航翻译使用正确的locale。
Dashboard页面和子组件
src/app/[locale]/dashboard/_components/dashboard-sections.tsx, src/app/[locale]/dashboard/audit-logs/page.tsx, src/app/[locale]/dashboard/availability/page.tsx, src/app/[locale]/dashboard/leaderboard/page.tsx, src/app/[locale]/dashboard/my-quota/page.tsx, src/app/[locale]/dashboard/rate-limits/page.tsx, src/app/[locale]/dashboard/providers/page.tsx
从route参数提取locale并更新getTranslations调用为{ locale, namespace: "..." }形式。
Quotas模块
src/app/[locale]/dashboard/quotas/layout.tsx, src/app/[locale]/dashboard/quotas/providers/page.tsx, src/app/[locale]/dashboard/quotas/users/page.tsx
布局和页面组件更新为接收并使用locale参数进行locale-aware翻译。
Settings页面
src/app/[locale]/settings/client-versions/page.tsx, src/app/[locale]/settings/config/page.tsx, src/app/[locale]/settings/error-rules/page.tsx, src/app/[locale]/settings/logs/page.tsx, src/app/[locale]/settings/prices/page.tsx, src/app/[locale]/settings/providers/page.tsx, src/app/[locale]/settings/request-filters/page.tsx, src/app/[locale]/settings/sensitive-words/page.tsx, src/app/[locale]/settings/status-page/page.tsx
页面组件添加route参数处理以提取locale,更新getTranslations为locale-aware调用。
Settings导航和状态页面
src/app/[locale]/settings/_lib/nav-items.ts, src/app/[locale]/status/[slug]/page.tsx
导航工具函数和状态页面更新为接收和使用locale参数。
代理错误处理
src/app/v1/_lib/proxy/error-handler.ts
添加Langfuse错误跟踪,新增emitErrorTrace方法用于记录代理错误。
代理响应处理和计费
src/app/v1/_lib/proxy/response-handler.ts
引入resolveBillableUsageMetricsForCost以支持缺少token usage时的per-request计费;整合Langfuse跟踪;扩展会话可观测性以包含costUsd
Langfuse集成
src/lib/langfuse/emit-proxy-trace.ts, src/lib/langfuse/trace-proxy-request.ts
新增模块定义EmitProxyLangfuseTraceDataemitProxyLangfuseTrace函数;添加响应输出构建和缺失响应检测的辅助函数。
排行榜和语言切换器
src/repository/leaderboard.ts, src/components/ui/language-switcher.tsx
排行榜排序改为优先按总成本(cost)而非请求数;语言切换器添加pendingLocale状态和sessionStorage支持以在locale变更后触发router.refresh()
测试 - 国际化和代理
tests/unit/i18n/locale-server-translations.test.ts, tests/unit/proxy/error-handler-langfuse-trace.test.ts
新增unit test验证locale服务器翻译模式;添加代理错误处理Langfuse跟踪的测试。
测试 - Langfuse和计费
tests/unit/langfuse/langfuse-trace.test.ts, tests/integration/billing-model-source.test.ts, tests/unit/repository/leaderboard-provider-metrics.test.ts, src/components/ui/__tests__/language-switcher.test.tsx
新增Langfuse跟踪单元测试、计费集成测试、排行榜单元测试及语言切换器组件测试。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR标题清晰准确,直接指出核心变更是修复首页模型排行榜的排序问题,与主要改动(从请求次数改为成本排序)高度相关。
Description check ✅ Passed PR描述简洁地说明了改动内容(由请求次数修改为成本),与代码变更相关,虽然内容简短但足以传达变更目的。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dashboard-model-ranking-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hank9999 hank9999 changed the base branch from main to dev April 27, 2026 18:45
@github-actions github-actions Bot added bug Something isn't working area:statistics size/S Small PR (< 200 lines) labels Apr 27, 2026
Comment thread src/repository/leaderboard.ts

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces explicit locale handling for server-side translations, a refined language switcher with session-based refresh persistence, and support for per-request billing in scenarios where token usage is unavailable. Additionally, it refactors Langfuse tracing into a centralized utility and updates the leaderboard sorting logic to prioritize total cost. Review feedback highlighted a logic issue in the session observability sync, where certain conditions prevent the recording of error statuses in Redis when usage or cost data is missing.

I am having trouble creating individual review comments. Click here to see my feedback.

src/app/v1/_lib/proxy/response-handler.ts (1343-1347)

medium

The condition (usageMetrics || costUsdStr !== undefined) prevents the session status from being updated to error in Redis when a request fails without usage or cost. It should be updated to always sync the status if session.sessionId is present. Additionally, ensure this Redis I/O is executed as a fire-and-forget task to avoid blocking the main logic in this performance-sensitive path.

        if (session.sessionId && session.shouldTrackSessionObservability()) {
References
  1. In performance-sensitive code paths like provider failover, non-critical I/O operations (e.g., releasing a session in Redis) should be executed as fire-and-forget tasks to avoid blocking the main logic.

src/app/v1/_lib/proxy/response-handler.ts (3678-3682)

medium

Similar to the issue in handleNonStream, the condition perRequestCostUsd !== undefined prevents updating the session status in Redis for requests that fail. The status should be updated regardless of whether a cost was calculated. Ensure this Redis update is performed as a fire-and-forget task to avoid blocking the main logic, as per repository performance guidelines.

    if (session.sessionId && session.shouldTrackSessionObservability()) {
References
  1. In performance-sensitive code paths like provider failover, non-critical I/O operations (e.g., releasing a session in Redis) should be executed as fire-and-forget tasks to avoid blocking the main logic.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94c8f930a9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/repository/leaderboard.ts
Comment thread src/repository/leaderboard.ts

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR changes the model leaderboard ordering to sort by total cost (with request count as a secondary sort) and adds a unit test for the expected order. The SQL ORDER BY currently uses raw sum(costUsd) while totalCost is computed with COALESCE(sum(costUsd), 0), which can produce inconsistent ordering for nullable cost data.

PR Size: S

  • Lines changed: 51
  • Files changed: 2

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

None.

High Priority Issues (Should Fix)

  • src/repository/leaderboard.ts:1156 (Confidence: 85) ORDER BY uses raw sum(costUsd) instead of the same COALESCE(sum(costUsd), 0) expression used for totalCost.

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Identified PR #1127 and applied label size/S (51 lines changed across 2 files).
  • Posted 1 inline review comment on src/repository/leaderboard.ts:1156 flagging inconsistent ordering when costUsd is nullable (ORDER BY sum(costUsd) vs selected COALESCE(sum(costUsd), 0)), with a concrete code fix.
  • Submitted the required summary review comment to the PR via gh pr review --comment.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR correctly fixes the model leaderboard sort order to rank by total cost (descending) with request count as tiebreaker, matching the user leaderboard (L367), provider leaderboard (L699), and provider model sub-rows (L750). The change is minimal (1 line), well-aligned with existing patterns, and includes an appropriate unit test.

PR Size: S

  • Lines changed: 51
  • Files changed: 2

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Validation Notes

  • The SELECT clause uses COALESCE(sum(costUsd), 0) while the ORDER BY uses bare sum(costUsd) -- this is a pre-existing pattern consistent across all leaderboard queries (L367, L407, L699, L750). Not introduced by this PR; not flagged.
  • Test coverage is adequate: verifies both result ordering (expensive-low-volume ranks above cheap-high-volume) and ORDER BY argument structure (sum + count).
  • No error handling, security, or type safety concerns in the diff scope.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Claude AI

@hank9999

Copy link
Copy Markdown
Collaborator Author

Issue was fixed in #1128

@ding113 ding113 merged commit af195f6 into dev Apr 28, 2026
22 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Apr 28, 2026
@github-actions github-actions Bot mentioned this pull request Apr 28, 2026
@ding113 ding113 deleted the fix/dashboard-model-ranking-order branch May 13, 2026 06:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:statistics bug Something isn't working size/S Small PR (< 200 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants