< Summary

Information
Class: OpenAiIntegration.TokenUsageTracker
Assembly: OpenAiIntegration
File(s): /home/runner/work/KicktippAi/KicktippAi/src/OpenAiIntegration/TokenUsageTracker.cs
Line coverage
100%
Covered lines: 94
Uncovered lines: 0
Coverable lines: 94
Total lines: 200
Line coverage: 100%
Branch coverage
100%
Covered branches: 20
Total branches: 20
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%44100%
AddUsage(...)100%44100%
GetCompactSummary()100%11100%
GetCompactSummaryWithEstimatedCosts(...)100%11100%
CalculateTotalEstimatedCost(...)100%44100%
GetLastUsageCompactSummary()100%11100%
GetLastUsageCompactSummaryWithEstimatedCosts(...)100%22100%
GetLastUsageJson()100%66100%
GetTotalCost()100%11100%
GetLastCost()100%11100%
Reset()100%11100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/OpenAiIntegration/TokenUsageTracker.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text.Json;
 3using Microsoft.Extensions.Logging;
 4using OpenAI.Chat;
 5
 6namespace OpenAiIntegration;
 7
 8/// <summary>
 9/// Service for tracking token usage and costs across multiple API calls
 10/// </summary>
 11public class TokenUsageTracker : ITokenUsageTracker
 12{
 13    private readonly ILogger<TokenUsageTracker> _logger;
 14    private readonly ICostCalculationService _costCalculationService;
 115    private readonly object _lock = new();
 16
 17    private int _totalUncachedInputTokens = 0;
 18    private int _totalCachedInputTokens = 0;
 19    private int _totalOutputReasoningTokens = 0;
 20    private int _totalOutputTokens = 0;
 21    private decimal _totalCost = 0m;
 22
 23    // Track last usage for individual match reporting
 24    private int _lastUncachedInputTokens = 0;
 25    private int _lastCachedInputTokens = 0;
 26    private int _lastOutputReasoningTokens = 0;
 27    private int _lastOutputTokens = 0;
 28    private decimal _lastCost = 0m;
 29    private ChatTokenUsage? _lastUsage = null;
 30
 131    public TokenUsageTracker(ILogger<TokenUsageTracker> logger, ICostCalculationService costCalculationService)
 32    {
 133        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 134        _costCalculationService = costCalculationService ?? throw new ArgumentNullException(nameof(costCalculationServic
 135    }
 36
 37    public void AddUsage(string model, ChatTokenUsage usage)
 38    {
 139        lock (_lock)
 40        {
 41            // Store last usage for individual reporting
 142            _lastUsage = usage;
 43
 44            // Get token counts
 145            var cachedInputTokens = usage.InputTokenDetails?.CachedTokenCount ?? 0;
 146            var uncachedInputTokens = usage.InputTokenCount - cachedInputTokens;
 147            var outputReasoningTokens = usage.OutputTokenDetails?.ReasoningTokenCount ?? 0;
 148            var regularOutputTokens = usage.OutputTokenCount - outputReasoningTokens;
 49
 50            // Store last usage for individual reporting
 151            _lastUncachedInputTokens = uncachedInputTokens;
 152            _lastCachedInputTokens = cachedInputTokens;
 153            _lastOutputReasoningTokens = outputReasoningTokens;
 154            _lastOutputTokens = regularOutputTokens;
 55
 56            // Add to totals
 157            _totalUncachedInputTokens += uncachedInputTokens;
 158            _totalCachedInputTokens += cachedInputTokens;
 159            _totalOutputReasoningTokens += outputReasoningTokens;
 160            _totalOutputTokens += regularOutputTokens;
 61
 62            // Calculate cost for this usage
 163            var costForThisUsage = _costCalculationService.CalculateCost(model, usage) ?? 0m;
 164            _lastCost = costForThisUsage;
 165            _totalCost += costForThisUsage;
 66
 167            _logger.LogDebug("Added usage for model {Model}: {UncachedInput} uncached + {CachedInput} cached + {OutputRe
 168                model, uncachedInputTokens, cachedInputTokens, outputReasoningTokens, regularOutputTokens, costForThisUs
 169        }
 170    }
 71
 72    public string GetCompactSummary()
 73    {
 174        lock (_lock)
 75        {
 176            return $"{_totalUncachedInputTokens.ToString("N0", CultureInfo.InvariantCulture)} / {_totalCachedInputTokens
 77        }
 178    }
 79
 80    public string GetCompactSummaryWithEstimatedCosts(string estimatedCostsModel)
 81    {
 182        lock (_lock)
 83        {
 184            var baseSummary = $"{_totalUncachedInputTokens.ToString("N0", CultureInfo.InvariantCulture)} / {_totalCached
 85
 86            // Calculate estimated costs for the alternative model
 187            decimal totalEstimatedCost = CalculateTotalEstimatedCost(estimatedCostsModel);
 88
 189            return $"{baseSummary} (est {estimatedCostsModel}: ${totalEstimatedCost.ToString("F4", CultureInfo.Invariant
 90        }
 191    }
 92
 93    private decimal CalculateTotalEstimatedCost(string estimatedCostsModel)
 94    {
 95        // Manually calculate estimated cost based on our tracked totals
 196        if (!ModelPricingData.Pricing.TryGetValue(estimatedCostsModel, out var pricing))
 97        {
 198            return 0m; // Can't calculate if we don't have pricing info
 99        }
 100
 101        // Calculate costs for each component
 1102        var uncachedInputCost = (_totalUncachedInputTokens / 1_000_000m) * pricing.InputPrice;
 1103        var cachedInputCost = pricing.CachedInputPrice.HasValue
 1104            ? (_totalCachedInputTokens / 1_000_000m) * pricing.CachedInputPrice.Value
 1105            : 0m;
 1106        var totalOutputTokenCount = _totalOutputReasoningTokens + _totalOutputTokens;
 1107        var outputCost = (totalOutputTokenCount / 1_000_000m) * pricing.OutputPrice;
 108
 1109        return uncachedInputCost + cachedInputCost + outputCost;
 110    }
 111
 112    public string GetLastUsageCompactSummary()
 113    {
 1114        lock (_lock)
 115        {
 1116            return $"{_lastUncachedInputTokens.ToString("N0", CultureInfo.InvariantCulture)} / {_lastCachedInputTokens.T
 117        }
 1118    }
 119
 120    public string GetLastUsageCompactSummaryWithEstimatedCosts(string estimatedCostsModel)
 121    {
 1122        lock (_lock)
 123        {
 1124            var baseSummary = $"{_lastUncachedInputTokens.ToString("N0", CultureInfo.InvariantCulture)} / {_lastCachedIn
 125
 126            // Calculate estimated cost for last usage
 1127            if (_lastUsage != null)
 128            {
 1129                var estimatedCost = _costCalculationService.CalculateCost(estimatedCostsModel, _lastUsage) ?? 0m;
 1130                return $"{baseSummary} (est {estimatedCostsModel}: ${estimatedCost.ToString("F4", CultureInfo.InvariantC
 131            }
 132
 1133            return baseSummary;
 134        }
 1135    }
 136
 137    public string? GetLastUsageJson()
 138    {
 1139        lock (_lock)
 140        {
 1141            if (_lastUsage == null)
 1142                return null;
 143
 1144            var usageData = new
 1145            {
 1146                InputTokenCount = _lastUsage.InputTokenCount,
 1147                OutputTokenCount = _lastUsage.OutputTokenCount,
 1148                InputTokenDetails = _lastUsage.InputTokenDetails != null ? new
 1149                {
 1150                    CachedTokenCount = _lastUsage.InputTokenDetails.CachedTokenCount,
 1151                    AudioTokenCount = _lastUsage.InputTokenDetails.AudioTokenCount
 1152                } : null,
 1153                OutputTokenDetails = _lastUsage.OutputTokenDetails != null ? new
 1154                {
 1155                    ReasoningTokenCount = _lastUsage.OutputTokenDetails.ReasoningTokenCount,
 1156                    AudioTokenCount = _lastUsage.OutputTokenDetails.AudioTokenCount
 1157                } : null
 1158            };
 159
 1160            return JsonSerializer.Serialize(usageData, new JsonSerializerOptions { WriteIndented = false });
 161        }
 1162    }
 163
 164    public decimal GetTotalCost()
 165    {
 1166        lock (_lock)
 167        {
 1168            return _totalCost;
 169        }
 1170    }
 171
 172    public decimal GetLastCost()
 173    {
 1174        lock (_lock)
 175        {
 1176            return _lastCost;
 177        }
 1178    }
 179
 180    public void Reset()
 181    {
 1182        lock (_lock)
 183        {
 1184            _totalUncachedInputTokens = 0;
 1185            _totalCachedInputTokens = 0;
 1186            _totalOutputReasoningTokens = 0;
 1187            _totalOutputTokens = 0;
 1188            _totalCost = 0m;
 189
 1190            _lastUncachedInputTokens = 0;
 1191            _lastCachedInputTokens = 0;
 1192            _lastOutputReasoningTokens = 0;
 1193            _lastOutputTokens = 0;
 1194            _lastCost = 0m;
 1195            _lastUsage = null;
 196
 1197            _logger.LogDebug("Token usage tracker reset");
 1198        }
 1199    }
 200}