From 5740b97cbb6f9aead35e89f252903f418d59ff4e Mon Sep 17 00:00:00 2001 From: hank9999 Date: Mon, 27 Apr 2026 20:06:47 +0100 Subject: [PATCH] fix(leaderboard): add COALESCE to ORDER BY for consistent NULL handling SELECT uses COALESCE(sum(costUsd), 0) but ORDER BY used raw sum(costUsd). In PostgreSQL DESC, NULL sorts first, causing models/users/providers with no billing data to rank above those with explicit $0.00 cost despite both returning totalCost=0 to the caller. --- src/repository/leaderboard.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/repository/leaderboard.ts b/src/repository/leaderboard.ts index 4c29545c0..370010496 100644 --- a/src/repository/leaderboard.ts +++ b/src/repository/leaderboard.ts @@ -364,7 +364,7 @@ async function findLeaderboardWithTimezone( .innerJoin(users, and(sql`${usageLedger.userId} = ${users.id}`, isNull(users.deletedAt))) .where(and(...whereConditions)) .groupBy(usageLedger.userId, users.name) - .orderBy(desc(sql`sum(${usageLedger.costUsd})`)); + .orderBy(desc(sql`COALESCE(sum(${usageLedger.costUsd}), 0)`)); const baseEntries: LeaderboardEntry[] = rankings.map((entry) => ({ userId: entry.userId, @@ -404,7 +404,7 @@ async function findLeaderboardWithTimezone( .innerJoin(users, and(sql`${usageLedger.userId} = ${users.id}`, isNull(users.deletedAt))) .where(and(...whereConditions)) .groupBy(usageLedger.userId, modelField) - .orderBy(desc(sql`sum(${usageLedger.costUsd})`)); + .orderBy(desc(sql`COALESCE(sum(${usageLedger.costUsd}), 0)`)); const modelStatsByUser = new Map(); for (const row of modelRows) { @@ -696,7 +696,7 @@ async function findProviderLeaderboardWithTimezone( and(...whereConditions.filter((c): c is NonNullable<(typeof whereConditions)[number]> => !!c)) ) .groupBy(usageLedger.finalProviderId, providers.name) - .orderBy(desc(sql`sum(${usageLedger.costUsd})`)); + .orderBy(desc(sql`COALESCE(sum(${usageLedger.costUsd}), 0)`)); const baseEntries: ProviderLeaderboardEntry[] = rankings.map((entry) => { const totalCost = parseFloat(entry.totalCost); @@ -747,7 +747,7 @@ async function findProviderLeaderboardWithTimezone( and(...whereConditions.filter((c): c is NonNullable<(typeof whereConditions)[number]> => !!c)) ) .groupBy(usageLedger.finalProviderId, modelField) - .orderBy(desc(sql`sum(${usageLedger.costUsd})`), desc(sql`count(*)`)); + .orderBy(desc(sql`COALESCE(sum(${usageLedger.costUsd}), 0)`), desc(sql`count(*)`)); const modelStatsByProvider = new Map(); for (const row of modelRows) { @@ -1153,7 +1153,7 @@ async function findModelLeaderboardWithTimezone( .from(usageLedger) .where(and(LEDGER_BILLING_CONDITION, buildDateCondition(period, timezone, dateRange))) .groupBy(modelField) - .orderBy(desc(sql`sum(${usageLedger.costUsd})`), desc(sql`count(*)`)); + .orderBy(desc(sql`COALESCE(sum(${usageLedger.costUsd}), 0)`), desc(sql`count(*)`)); return rankings .filter((entry) => entry.model !== null && entry.model !== "")