UNPKG

cost-claude

Version:

Claude Code cost monitoring, analytics, and optimization toolkit

247 lines 10.3 kB
import { ProjectParser } from '../core/project-parser.js'; import { formatDate } from '../utils/format.js'; export class GroupAnalyzer { parser; calculator; constructor(parser, calculator) { this.parser = parser; this.calculator = calculator; } groupByProject(messages) { const grouped = ProjectParser.groupByProject(messages); const results = []; grouped.forEach((projectMessages, projectName) => { const stats = this.calculateGroupStats(projectMessages, projectName); if (stats.messageCount > 0) { results.push(stats); } }); return results.sort((a, b) => b.totalCost - a.totalCost); } groupByDate(messages) { const grouped = new Map(); messages.forEach(msg => { if (!msg.timestamp) return; const timestamp = new Date(msg.timestamp); if (isNaN(timestamp.getTime())) return; const date = formatDate(timestamp); if (date === 'Invalid Date') return; if (!grouped.has(date)) { grouped.set(date, []); } grouped.get(date).push(msg); }); const results = []; grouped.forEach((dateMessages, date) => { const stats = this.calculateGroupStats(dateMessages, date); if (stats.messageCount > 0) { results.push(stats); } }); return results.sort((a, b) => a.groupName.localeCompare(b.groupName)); } groupByHour(messages) { const grouped = new Map(); messages.forEach(msg => { if (!msg.timestamp) return; const timestamp = new Date(msg.timestamp); if (isNaN(timestamp.getTime())) return; const date = formatDate(timestamp); if (date === 'Invalid Date') return; const hourKey = `${date} ${timestamp.getHours().toString().padStart(2, '0')}:00`; if (!grouped.has(hourKey)) { grouped.set(hourKey, []); } grouped.get(hourKey).push(msg); }); const results = []; grouped.forEach((hourMessages, hour) => { const stats = this.calculateGroupStats(hourMessages, hour); if (stats.messageCount > 0) { results.push(stats); } }); return results.sort((a, b) => a.groupName.localeCompare(b.groupName)); } groupBySession(messages, filterLastWeek = false) { const grouped = this.parser.groupBySession(messages); const results = []; grouped.forEach((sessionMessages, sessionId) => { const stats = this.calculateGroupStats(sessionMessages, sessionId); if (stats.messageCount > 0) { if (sessionMessages.length > 0) { const firstMessage = sessionMessages[0]; if (firstMessage) { stats.projectName = ProjectParser.getProjectFromMessage(firstMessage); } if (stats.startTime && stats.endTime) { const start = new Date(stats.startTime); const end = new Date(stats.endTime); if (formatDate(start) === formatDate(end)) { stats.dateRange = formatDate(start); } else { const startMonth = start.getMonth() + 1; const startDay = start.getDate(); const endMonth = end.getMonth() + 1; const endDay = end.getDate(); if (start.getFullYear() === end.getFullYear() && startMonth === endMonth) { stats.dateRange = `${start.getFullYear()}-${String(startMonth).padStart(2, '0')}-${String(startDay).padStart(2, '0')}~${String(endDay).padStart(2, '0')}`; } else { stats.dateRange = `${String(startMonth).padStart(2, '0')}/${String(startDay).padStart(2, '0')}~${String(endMonth).padStart(2, '0')}/${String(endDay).padStart(2, '0')}`; } } } } if (filterLastWeek && stats.endTime) { const endDate = new Date(stats.endTime); const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); oneWeekAgo.setHours(0, 0, 0, 0); if (endDate < oneWeekAgo) { return; } } results.push(stats); } }); return results.sort((a, b) => b.totalCost - a.totalCost); } calculateGroupStats(messages, groupName) { const assistantMessages = this.parser.filterByType(messages, 'assistant'); let totalCost = 0; let totalInputTokens = 0; let totalOutputTokens = 0; let totalCacheTokens = 0; let totalDuration = 0; let totalCacheCreationTokens = 0; let inputCost = 0; let outputCost = 0; let cacheWriteCost = 0; let cacheReadCost = 0; assistantMessages.forEach(msg => { if (msg.costUSD !== null && msg.costUSD !== undefined) { totalCost += msg.costUSD; } else { const content = this.parser.parseMessageContent(msg); if (content?.usage) { totalCost += this.calculator.calculate(content.usage); } } if (msg.durationMs) { totalDuration += msg.durationMs; } const content = this.parser.parseMessageContent(msg); if (content?.usage) { totalInputTokens += content.usage.input_tokens || 0; totalOutputTokens += content.usage.output_tokens || 0; totalCacheTokens += content.usage.cache_read_input_tokens || 0; totalCacheCreationTokens += content.usage.cache_creation_input_tokens || 0; const breakdown = this.calculator.calculateBreakdown(content.usage); inputCost += breakdown.inputTokensCost; outputCost += breakdown.outputTokensCost; cacheWriteCost += breakdown.cacheCreationCost; cacheReadCost += breakdown.cacheReadCost; } }); const cacheEfficiency = this.calculator.calculateCacheEfficiency({ input_tokens: totalInputTokens, cache_read_input_tokens: totalCacheTokens, cache_creation_input_tokens: totalCacheCreationTokens, }); const sorted = this.parser.sortByTimestamp(messages); const startTime = sorted.length > 0 ? sorted[0]?.timestamp : undefined; const endTime = sorted.length > 0 ? sorted[sorted.length - 1]?.timestamp : undefined; const duration = this.parser.calculateSessionDuration(messages); return { groupName, totalCost, messageCount: assistantMessages.length, avgCost: assistantMessages.length > 0 ? totalCost / assistantMessages.length : 0, duration, tokens: { input: totalInputTokens, output: totalOutputTokens, cache: totalCacheTokens, cacheWrite: totalCacheCreationTokens, }, cacheEfficiency, costBreakdown: { inputCost, outputCost, cacheWriteCost, cacheReadCost, }, startTime, endTime, }; } groupByModel(messages) { const grouped = new Map(); messages.forEach(msg => { if (msg.type === 'assistant') { const content = this.parser.parseMessageContent(msg); const model = content?.model || 'unknown'; if (!grouped.has(model)) { grouped.set(model, []); } grouped.get(model).push(msg); } }); const results = []; grouped.forEach((modelMessages, model) => { const stats = this.calculateGroupStats(modelMessages, model); if (stats.messageCount > 0) { results.push(stats); } }); return results.sort((a, b) => b.totalCost - a.totalCost); } groupByCostRange(messages) { const ranges = [ { name: 'High (>$1)', min: 1, max: Infinity }, { name: 'Medium ($0.1-$1)', min: 0.1, max: 1 }, { name: 'Low ($0.01-$0.1)', min: 0.01, max: 0.1 }, { name: 'Very Low (<$0.01)', min: 0, max: 0.01 }, ]; const grouped = new Map(); ranges.forEach(range => grouped.set(range.name, [])); messages.forEach(msg => { if (msg.type === 'assistant') { let msgCost = 0; if (msg.costUSD !== null && msg.costUSD !== undefined) { msgCost = msg.costUSD; } else { const content = this.parser.parseMessageContent(msg); if (content?.usage) { msgCost = this.calculator.calculate(content.usage); } } if (msgCost > 0) { const range = ranges.find(r => msgCost >= r.min && msgCost < r.max); if (range) { grouped.get(range.name).push(msg); } } } }); const results = []; grouped.forEach((rangeMessages, rangeName) => { const stats = this.calculateGroupStats(rangeMessages, rangeName); if (stats.messageCount > 0) { results.push(stats); } }); return results; } } //# sourceMappingURL=group-analyzer.js.map