fix: 修复会话窗口使用统计问题

This commit is contained in:
shaw
2025-08-31 20:14:12 +08:00
parent 07e9bc1137
commit 9a46310238
4 changed files with 66 additions and 9 deletions

View File

@@ -1431,7 +1431,13 @@ class RedisClient {
const startDate = new Date(windowStart) const startDate = new Date(windowStart)
const endDate = new Date(windowEnd) const endDate = new Date(windowEnd)
// 添加日志以调试时间窗口
logger.debug(`📊 Getting session window usage for account ${accountId}`)
logger.debug(` Window: ${windowStart} to ${windowEnd}`)
logger.debug(` Start UTC: ${startDate.toISOString()}, End UTC: ${endDate.toISOString()}`)
// 获取窗口内所有可能的小时键 // 获取窗口内所有可能的小时键
// 重要:需要使用配置的时区来构建键名,因为数据存储时使用的是配置时区
const hourlyKeys = [] const hourlyKeys = []
const currentHour = new Date(startDate) const currentHour = new Date(startDate)
currentHour.setMinutes(0) currentHour.setMinutes(0)
@@ -1439,9 +1445,12 @@ class RedisClient {
currentHour.setMilliseconds(0) currentHour.setMilliseconds(0)
while (currentHour <= endDate) { while (currentHour <= endDate) {
const dateStr = `${currentHour.getUTCFullYear()}-${String(currentHour.getUTCMonth() + 1).padStart(2, '0')}-${String(currentHour.getUTCDate()).padStart(2, '0')}` // 使用时区转换函数来获取正确的日期和小时
const hourStr = String(currentHour.getUTCHours()).padStart(2, '0') const tzDateStr = getDateStringInTimezone(currentHour)
const key = `account_usage:hourly:${accountId}:${dateStr}:${hourStr}` const tzHour = String(getHourInTimezone(currentHour)).padStart(2, '0')
const key = `account_usage:hourly:${accountId}:${tzDateStr}:${tzHour}`
logger.debug(` Adding hourly key: ${key}`)
hourlyKeys.push(key) hourlyKeys.push(key)
currentHour.setHours(currentHour.getHours() + 1) currentHour.setHours(currentHour.getHours() + 1)
} }
@@ -1462,18 +1471,31 @@ class RedisClient {
let totalRequests = 0 let totalRequests = 0
const modelUsage = {} const modelUsage = {}
logger.debug(` Processing ${results.length} hourly results`)
for (const [error, data] of results) { for (const [error, data] of results) {
if (error || !data || Object.keys(data).length === 0) { if (error || !data || Object.keys(data).length === 0) {
continue continue
} }
// 处理总计数据 // 处理总计数据
totalInputTokens += parseInt(data.inputTokens || 0) const hourInputTokens = parseInt(data.inputTokens || 0)
totalOutputTokens += parseInt(data.outputTokens || 0) const hourOutputTokens = parseInt(data.outputTokens || 0)
totalCacheCreateTokens += parseInt(data.cacheCreateTokens || 0) const hourCacheCreateTokens = parseInt(data.cacheCreateTokens || 0)
totalCacheReadTokens += parseInt(data.cacheReadTokens || 0) const hourCacheReadTokens = parseInt(data.cacheReadTokens || 0)
totalAllTokens += parseInt(data.allTokens || 0) const hourAllTokens = parseInt(data.allTokens || 0)
totalRequests += parseInt(data.requests || 0) const hourRequests = parseInt(data.requests || 0)
totalInputTokens += hourInputTokens
totalOutputTokens += hourOutputTokens
totalCacheCreateTokens += hourCacheCreateTokens
totalCacheReadTokens += hourCacheReadTokens
totalAllTokens += hourAllTokens
totalRequests += hourRequests
if (hourAllTokens > 0) {
logger.debug(` Hour data: allTokens=${hourAllTokens}, requests=${hourRequests}`)
}
// 处理每个模型的数据 // 处理每个模型的数据
for (const [key, value] of Object.entries(data)) { for (const [key, value] of Object.entries(data)) {
@@ -1513,6 +1535,14 @@ class RedisClient {
} }
} }
logger.debug(`📊 Session window usage summary:`)
logger.debug(` Total allTokens: ${totalAllTokens}`)
logger.debug(` Total requests: ${totalRequests}`)
logger.debug(` Input: ${totalInputTokens}, Output: ${totalOutputTokens}`)
logger.debug(
` Cache Create: ${totalCacheCreateTokens}, Cache Read: ${totalCacheReadTokens}`
)
return { return {
totalInputTokens, totalInputTokens,
totalOutputTokens, totalOutputTokens,

View File

@@ -1524,7 +1524,10 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
cache_read_input_tokens: usage.cacheReadTokens cache_read_input_tokens: usage.cacheReadTokens
} }
logger.debug(`💰 Calculating cost for model ${modelName}:`, JSON.stringify(usageData))
const costResult = CostCalculator.calculateCost(usageData, modelName) const costResult = CostCalculator.calculateCost(usageData, modelName)
logger.debug(`💰 Cost result for ${modelName}: total=${costResult.costs.total}`)
modelCosts[modelName] = { modelCosts[modelName] = {
...usage, ...usage,
cost: costResult.costs.total cost: costResult.costs.total

View File

@@ -45,6 +45,7 @@ class PricingService {
'claude-sonnet-3-5': 0.000006, 'claude-sonnet-3-5': 0.000006,
'claude-sonnet-3-7': 0.000006, 'claude-sonnet-3-7': 0.000006,
'claude-sonnet-4': 0.000006, 'claude-sonnet-4': 0.000006,
'claude-sonnet-4-20250514': 0.000006,
// Haiku 系列: $1.6/MTok // Haiku 系列: $1.6/MTok
'claude-3-5-haiku': 0.0000016, 'claude-3-5-haiku': 0.0000016,
@@ -260,6 +261,7 @@ class PricingService {
// 尝试直接匹配 // 尝试直接匹配
if (this.pricingData[modelName]) { if (this.pricingData[modelName]) {
logger.debug(`💰 Found exact pricing match for ${modelName}`)
return this.pricingData[modelName] return this.pricingData[modelName]
} }
@@ -304,6 +306,20 @@ class PricingService {
return null return null
} }
// 确保价格对象包含缓存价格
ensureCachePricing(pricing) {
if (!pricing) return pricing
// 如果缺少缓存价格根据输入价格计算缓存创建价格通常是输入价格的1.25倍缓存读取是0.1倍)
if (!pricing.cache_creation_input_token_cost && pricing.input_cost_per_token) {
pricing.cache_creation_input_token_cost = pricing.input_cost_per_token * 1.25
}
if (!pricing.cache_read_input_token_cost && pricing.input_cost_per_token) {
pricing.cache_read_input_token_cost = pricing.input_cost_per_token * 0.1
}
return pricing
}
// 获取 1 小时缓存价格 // 获取 1 小时缓存价格
getEphemeral1hPricing(modelName) { getEphemeral1hPricing(modelName) {
if (!modelName) { if (!modelName) {

View File

@@ -31,6 +31,14 @@ const MODEL_PRICING = {
cacheWrite: 18.75, cacheWrite: 18.75,
cacheRead: 1.5 cacheRead: 1.5
}, },
// Claude Opus 4.1 (新模型)
'claude-opus-4-1-20250805': {
input: 15.0,
output: 75.0,
cacheWrite: 18.75,
cacheRead: 1.5
},
// Claude 3 Sonnet // Claude 3 Sonnet
'claude-3-sonnet-20240229': { 'claude-3-sonnet-20240229': {