Module: LlmCostTracker::Pricing::ServiceCharges

Extended by:
ServiceCharges
Included in:
LlmCostTracker::Pricing, ServiceCharges
Defined in:
lib/llm_cost_tracker/pricing/service_charges.rb

Constant Summary collapse

DEFAULT_CURRENCY =
"USD"
EMPTY_RATES =
{}.freeze
MUTEX =
Mutex.new

Instance Method Summary collapse

Instance Method Details

#builtin_ratesObject



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/llm_cost_tracker/pricing/service_charges.rb', line 27

def builtin_rates
  cached = @builtin_rates
  return cached if cached

  MUTEX.synchronize do
    @builtin_rates ||= begin
      registry = YAML.safe_load_file(Registry::DEFAULT_PRICES_PATH, aliases: false) || {}
      rates_from_registry(registry).freeze
    end
  end
end

#charge_rate(provider:, component:, pricing_mode:) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/llm_cost_tracker/pricing/service_charges.rb', line 69

def charge_rate(provider:, component:, pricing_mode:)
  pricing_mode = Pricing.normalize_mode(pricing_mode)
  match = charge_rate_match(provider: provider, component: component, pricing_mode: pricing_mode)
  return nil unless match

  rate = match.fetch(:rate)
  {
    amount: rate.fetch(:amount),
    quantity: rate.fetch(:quantity),
    currency: rate.fetch(:currency),
    source: match.fetch(:source),
    source_key: match.fetch(:key),
    source_version: rate_source_version_for(match.fetch(:source))
  }
end

#file_rates(path) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/llm_cost_tracker/pricing/service_charges.rb', line 39

def file_rates(path)
  return EMPTY_RATES unless path

  cache_key = [path, File.mtime(path)]
  cached = @file_rates_cache
  return cached[:value] if cached && cached[:key] == cache_key

  MUTEX.synchronize do
    cached = @file_rates_cache
    return cached[:value] if cached && cached[:key] == cache_key

    registry = YAML.safe_load_file(path, aliases: false) || {}
    value = rates_from_registry(registry, context: path).freeze
    @file_rates_cache = { key: cache_key, value: value }.freeze
    value
  end
rescue Errno::ENOENT, Psych::Exception, ArgumentError, TypeError => e
  raise Error, "Unable to load prices_file #{path.inspect}: #{e.message}"
end

#rates_from_registry(registry, context: "price registry") ⇒ Object

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
# File 'lib/llm_cost_tracker/pricing/service_charges.rb', line 59

def rates_from_registry(registry, context: "price registry")
  data = registry.fetch("service_charges", EMPTY_RATES)
  raise ArgumentError, "#{context} service_charges must be a hash" unless data.is_a?(Hash)

  data.each_with_object({}) do |(provider, entries), rates|
    section_context = "#{context} service_charges.#{provider}"
    rates[provider] = rates_from_section(entries, context: section_context)
  end
end

#reset!Object



20
21
22
23
24
25
# File 'lib/llm_cost_tracker/pricing/service_charges.rb', line 20

def reset!
  MUTEX.synchronize do
    @builtin_rates = nil
    @file_rates_cache = nil
  end
end