< Summary

Information
Class: OpenAiIntegration.ModelPricing
Assembly: OpenAiIntegration
File(s): /home/runner/work/KicktippAi/KicktippAi/src/OpenAiIntegration/CostCalculationService.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 119
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_InputPrice()100%11100%
set_InputPrice(...)100%210%
get_OutputPrice()100%11100%
set_OutputPrice(...)100%210%
get_CachedInputPrice()100%11100%
set_CachedInputPrice(...)100%210%
.ctor(...)100%210%

File(s)

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

#LineLine coverage
 1using System.Globalization;
 2using Microsoft.Extensions.Logging;
 3using OpenAI.Chat;
 4
 5namespace OpenAiIntegration;
 6
 7/// <summary>
 8/// Service for calculating and logging OpenAI API costs
 9/// </summary>
 10public class CostCalculationService : ICostCalculationService
 11{
 12    private readonly ILogger<CostCalculationService> _logger;
 13
 14    public CostCalculationService(ILogger<CostCalculationService> logger)
 15    {
 16        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 17    }
 18
 19    public void LogCostBreakdown(string model, ChatTokenUsage usage)
 20    {
 21        if (ModelPricingData.Pricing.TryGetValue(model, out var pricing))
 22        {
 23            // Get exact token counts from usage details
 24            var cachedInputTokens = usage.InputTokenDetails?.CachedTokenCount ?? 0;
 25            var uncachedInputTokens = usage.InputTokenCount - cachedInputTokens;
 26            var reasoningOutputTokens = usage.OutputTokenDetails?.ReasoningTokenCount ?? 0;
 27            var textOutputTokens = usage.OutputTokenCount - reasoningOutputTokens;
 28
 29            // Calculate costs for each component
 30            var uncachedInputCost = (uncachedInputTokens / 1_000_000m) * pricing.InputPrice;
 31            var cachedInputCost = pricing.CachedInputPrice.HasValue
 32                ? (cachedInputTokens / 1_000_000m) * pricing.CachedInputPrice.Value
 33                : 0m;
 34            var outputCost = (usage.OutputTokenCount / 1_000_000m) * pricing.OutputPrice;
 35            var totalCost = uncachedInputCost + cachedInputCost + outputCost;
 36
 37            // Log the cost breakdown
 38            _logger.LogInformation("Uncached Input Tokens: {UncachedInputTokens:N0} × ${InputPrice:F2}/1M = ${UncachedIn
 39                uncachedInputTokens, pricing.InputPrice, uncachedInputCost);
 40
 41            if (pricing.CachedInputPrice.HasValue)
 42            {
 43                _logger.LogInformation("Cached Input Tokens: {CachedInputTokens:N0} × ${CachedInputPrice:F3}/1M = ${Cach
 44                    cachedInputTokens, pricing.CachedInputPrice.Value, cachedInputCost);
 45            }
 46
 47            _logger.LogInformation("Reasoning Output Tokens: {ReasoningOutputTokens:N0}",
 48                reasoningOutputTokens);
 49
 50            _logger.LogInformation("Text Output Tokens: {TextOutputTokens:N0}",
 51                textOutputTokens);
 52
 53            _logger.LogInformation("Total Output Tokens: {TotalOutputTokens:N0} × ${OutputPrice:F2}/1M = ${OutputCost:F6
 54                usage.OutputTokenCount, pricing.OutputPrice, outputCost);
 55
 56            _logger.LogInformation("Total Cost: ${TotalCost:F6}", totalCost);
 57        }
 58        else
 59        {
 60            _logger.LogWarning("Cost calculation not available: Pricing information not found for model '{Model}'", mode
 61        }
 62    }
 63
 64    public decimal? CalculateCost(string model, ChatTokenUsage usage)
 65    {
 66        if (ModelPricingData.Pricing.TryGetValue(model, out var pricing))
 67        {
 68            // Get exact token counts from usage details
 69            var cachedInputTokens = usage.InputTokenDetails?.CachedTokenCount ?? 0;
 70            var uncachedInputTokens = usage.InputTokenCount - cachedInputTokens;
 71            var outputTokens = usage.OutputTokenCount;
 72
 73            // Calculate costs for each component
 74            var uncachedInputCost = (uncachedInputTokens / 1_000_000m) * pricing.InputPrice;
 75            var cachedInputCost = pricing.CachedInputPrice.HasValue
 76                ? (cachedInputTokens / 1_000_000m) * pricing.CachedInputPrice.Value
 77                : 0m;
 78            var outputCost = (outputTokens / 1_000_000m) * pricing.OutputPrice;
 79
 80            return uncachedInputCost + cachedInputCost + outputCost;
 81        }
 82
 83        return null;
 84    }
 85}
 86
 87/// <summary>
 88/// Static pricing data for OpenAI models - matches the structure from PromptSampleTests
 89/// </summary>
 90internal static class ModelPricingData
 91{
 92    public static readonly Dictionary<string, ModelPricing> Pricing = new()
 93    {
 94        ["gpt-4.1"] = new(2.00m, 8.00m, 0.50m),
 95        ["gpt-4.1-mini"] = new(0.40m, 1.60m, 0.10m),
 96        ["gpt-4.1-nano"] = new(0.10m, 0.40m, 0.025m),
 97        ["gpt-4.5-preview"] = new(75.00m, 150.00m, 37.50m),
 98        ["gpt-4o"] = new(2.50m, 10.00m, 1.25m),
 99        ["gpt-4o-mini"] = new(0.15m, 0.60m, 0.075m),
 100        ["gpt-5"] = new(1.25m, 10.00m, 0.125m),
 101        ["gpt-5-mini"] = new(0.25m, 2.00m, 0.025m),
 102        ["gpt-5-nano"] = new(0.05m, 0.40m, 0.005m),
 103        ["o1"] = new(15.00m, 60.00m, 7.50m),
 104        ["o1-pro"] = new(150.00m, 600.00m),
 105        ["o3"] = new(2.00m, 8.00m, 0.50m),
 106        ["o3-pro"] = new(20.00m, 80.00m),
 107        ["o4-mini"] = new(1.10m, 4.40m, 0.275m),
 108        ["o3-mini"] = new(1.10m, 4.40m, 0.55m),
 109        ["o1-mini"] = new(1.10m, 4.40m, 0.55m),
 110    };
 111}
 112
 113/// <summary>
 114/// Pricing information for an OpenAI model
 115/// </summary>
 116/// <param name="InputPrice">Price per 1M input tokens</param>
 117/// <param name="OutputPrice">Price per 1M output tokens</param>
 118/// <param name="CachedInputPrice">Price per 1M cached input tokens (if supported)</param>
 1119internal record ModelPricing(decimal InputPrice, decimal OutputPrice, decimal? CachedInputPrice = null);