test(ai-review): 補使用量統計 NaN 安全測試並排除微優化誤報 #46

Merged
jiantw83 merged 14 commits from ai-review-resolve/20260623-163642 into develop 2026-06-23 09:35:21 +00:00
Showing only changes of commit 6259cd4398 - Show all commits
+17 -8
View File
@@ -211,23 +211,32 @@ function round1(n) {
const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' }; const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };
admin marked this conversation as resolved
Review

嚴重等級🟡 警告
審查員:Rogue
問題:在函式內部重複呼叫 Number(n),造成不必要的型別轉換開銷,且在條件式之後又呼叫一次 Number(quota.limit),造成重複轉換。
建議:建議將 Number(n) 的結果暫存起來,或在進入條件式後立即將值賦值給一個變數並重複使用,減少重複轉換的成本。

**嚴重等級**:🟡 警告 **審查員**:Rogue **問題**:在函式內部重複呼叫 `Number(n)`,造成不必要的型別轉換開銷,且在條件式之後又呼叫一次 `Number(quota.limit)`,造成重複轉換。 **建議**:建議將 `Number(n)` 的結果暫存起來,或在進入條件式後立即將值賦值給一個變數並重複使用,減少重複轉換的成本。
/** 是否為有限正數(排除 0、負數、NaN、Infinity),避免算出 Infinity%NaN%/負百分比。 */
function isFinitePositive(n) {
return Number.isFinite(Number(n)) && Number(n) > 0;
}
/** /**
* 計算「剩餘可用百分比」,依優先序擇一: * 計算「剩餘可用百分比」,依優先序擇一:
* 1. 帳號額度(quota 有上限)→ 剩餘 credits / 上限; * 1. 帳號額度(quota 有有限正數上限)→ 剩餘 credits / 上限;
* 2. 速率配額(rate limit header)→ 當前視窗剩餘 / 上限; * 2. 速率配額(rate limit header,有限正數上限)→ 當前視窗剩餘 / 上限;
* 皆無法取得時回傳 { percent: null, reason }。 * 上限或剩餘為 0/負數/NaN/Infinity 等無效值時不計算,落到 { percent: null, reason }。
*/ */
export function resolveRemainingPercent(quota, rate) { export function resolveRemainingPercent(quota, rate) {
if (quota?.available && quota.limit != null && Number(quota.limit) > 0) { if (quota?.available && quota.limit != null && isFinitePositive(quota.limit)) {
const limit = Number(quota.limit); const limit = Number(quota.limit);
const remaining = quota.remaining == null ? limit - num(quota.used) : Number(quota.remaining); const remaining = quota.remaining == null ? limit - num(quota.used) : Number(quota.remaining);
return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' }; if (Number.isFinite(remaining)) {
return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' };
}
} }
if (rate?.hasData && Number(rate.limit) > 0) { if (rate?.hasData && isFinitePositive(rate.limit)) {
const limit = Number(rate.limit); const limit = Number(rate.limit);
const remaining = Number(rate.remaining); const remaining = Number(rate.remaining);
const kindLabel = RATE_KIND_LABEL[rate.kind] || rate.kind; if (Number.isFinite(remaining)) {
return { percent: round1((remaining / limit) * 100), basis: `速率配額(當前視窗,${kindLabel}`, remaining, limit, unit: '' }; const kindLabel = RATE_KIND_LABEL[rate.kind] || rate.kind;
return { percent: round1((remaining / limit) * 100), basis: `速率配額(當前視窗,${kindLabel}`, remaining, limit, unit: '' };
}
} }
let reason; let reason;
if (quota?.available && quota.limit == null) reason = '帳號額度無上限,無法計算百分比'; if (quota?.available && quota.limit == null) reason = '帳號額度無上限,無法計算百分比';