UNPKG

claude-usage-tracker

Version:

Advanced analytics for Claude Code usage with cost optimization, conversation length analysis, and rate limit tracking

97 lines 4.5 kB
import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { calculateCost, aggregateDailyUsage, analyzeModelEfficiency, analyzeHourlyUsage, } from "./analyzer.js"; import { setupTestConfig } from "./test-utils.js"; describe("Analyzer", () => { let cleanup; beforeEach(() => { cleanup = setupTestConfig(); }); afterEach(() => { cleanup(); }); const mockEntries = [ { timestamp: "2024-01-15T10:00:00Z", model: "claude-3.5-sonnet-20241022", conversationId: "conv-1", requestId: "req-1", prompt_tokens: 1000, completion_tokens: 500, total_tokens: 1500, }, { timestamp: "2024-01-15T11:00:00Z", model: "claude-opus-4-20250514", conversationId: "conv-2", requestId: "req-2", prompt_tokens: 800, completion_tokens: 400, total_tokens: 1200, }, { timestamp: "2024-01-15T10:30:00Z", model: "claude-3.5-sonnet-20241022", conversationId: "conv-1", requestId: "req-3", prompt_tokens: 1000, // Same as first entry completion_tokens: 500, // Same as first entry total_tokens: 1500, // Same as first entry isBatchAPI: true, }, ]; describe("calculateCost", () => { it("should calculate cost correctly for regular API", () => { const cost = calculateCost(mockEntries[0]); // Sonnet: input 3.0, output 15.0 per million tokens // 1000 input + 500 output = (1000/1M * 3.0) + (500/1M * 15.0) = 0.003 + 0.0075 = 0.0105 expect(cost).toBeCloseTo(0.0105, 4); }); it("should apply batch API discount", () => { const regularCost = calculateCost(mockEntries[0]); const batchCost = calculateCost(mockEntries[2]); expect(batchCost).toBeLessThan(regularCost); expect(batchCost).toBeCloseTo(regularCost * 0.5, 4); // 50% discount }); }); describe("analyzeModelEfficiency", () => { it("should calculate efficiency metrics correctly", () => { const efficiency = analyzeModelEfficiency(mockEntries); expect(efficiency).toHaveLength(2); // Two models const sonnetEfficiency = efficiency.find((e) => e.model === "claude-3.5-sonnet-20241022"); const opusEfficiency = efficiency.find((e) => e.model === "claude-opus-4-20250514"); expect(sonnetEfficiency).toBeDefined(); expect(opusEfficiency).toBeDefined(); // Check that totalTokens are properly calculated (not NaN) expect(sonnetEfficiency.avgTokensPerConversation).toBeGreaterThan(0); expect(sonnetEfficiency.costPerToken).toBeGreaterThan(0); expect(Number.isFinite(sonnetEfficiency.avgTokensPerConversation)).toBe(true); expect(Number.isFinite(sonnetEfficiency.costPerToken)).toBe(true); }); }); describe("analyzeHourlyUsage", () => { it("should track hourly usage correctly", () => { const hourlyUsage = analyzeHourlyUsage(mockEntries); expect(hourlyUsage).toHaveLength(24); // All 24 hours const hour10 = hourlyUsage.find((h) => h.hour === 10); const hour11 = hourlyUsage.find((h) => h.hour === 11); expect(hour10).toBeDefined(); expect(hour11).toBeDefined(); // Hour 10 has 2 entries (regular + batch), hour 11 has 1 expect(hour10.totalTokens).toBe(1500 + 1500); // Both sonnet entries (same tokens now) expect(hour11.totalTokens).toBe(1200); // Opus entry expect(hour10.conversationCount).toBe(1); // Same conversation expect(hour11.conversationCount).toBe(1); // Different conversation }); }); describe("aggregateDailyUsage", () => { it("should aggregate daily usage correctly", () => { const dailyUsage = aggregateDailyUsage(mockEntries); expect(dailyUsage.size).toBe(1); // All entries on same day const day = dailyUsage.get("2024-01-15"); expect(day).toBeDefined(); expect(day.totalTokens).toBe(4200); // Sum of all tokens (1500+1200+1500) expect(day.conversationCount).toBe(2); // Two unique conversations }); }); }); //# sourceMappingURL=analyzer.test.js.map