Module: LlmCostTracker::Reconciliation::Sources::OpenaiUsage
- Defined in:
- lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb
Constant Summary collapse
- FINGERPRINT_KEYS =
%i[start_time end_time line_item model project_id api_key_id organization_id].freeze
- ROW_TYPE_COST =
"cost"- AUTHORITY_COST_API =
"cost_api"- DEFAULT_METER =
"tokens"
Class Method Summary collapse
- .end_inclusive_date(value) ⇒ Object
- .epoch_to_date(value) ⇒ Object
- .fingerprint_for(result, start_time:, end_time:) ⇒ Object
- .match_basis_for(result) ⇒ Object
- .metadata_for(result, authority:, row_type:) ⇒ Object
- .meter_for(result) ⇒ Object
- .parse(response, authority: AUTHORITY_COST_API, row_type: ROW_TYPE_COST) ⇒ Object
- .row_for_result(raw, period_start:, period_end:, start_time:, end_time:, authority:, row_type:) ⇒ Object
- .rows_for_bucket(bucket, authority:, row_type:) ⇒ Object
Class Method Details
.end_inclusive_date(value) ⇒ Object
107 108 109 110 111 112 113 114 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 107 def end_inclusive_date(value) time = if value.is_a?(Numeric) || value.to_s.match?(/\A\d+\z/) Time.at(Integer(value)).utc else Time.parse(value.to_s).utc end (time - 1).utc.to_date end |
.epoch_to_date(value) ⇒ Object
101 102 103 104 105 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 101 def epoch_to_date(value) return Time.at(Integer(value)).utc.to_date if value.is_a?(Numeric) || value.to_s.match?(/\A\d+\z/) Time.parse(value.to_s).utc.to_date end |
.fingerprint_for(result, start_time:, end_time:) ⇒ Object
95 96 97 98 99 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 95 def fingerprint_for(result, start_time:, end_time:) attributes = result.merge(start_time: Coercion.normalized_epoch(start_time), end_time: Coercion.normalized_epoch(end_time)) Fingerprint.compute(FINGERPRINT_KEYS, attributes) end |
.match_basis_for(result) ⇒ Object
87 88 89 90 91 92 93 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 87 def match_basis_for(result) return "project" if result[:project_id] return "api_key" if result[:api_key_id] return "model" if result[:model] "period_only" end |
.metadata_for(result, authority:, row_type:) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 63 def (result, authority:, row_type:) { "row_type" => row_type, "meter" => meter_for(result), "authority" => , "match_basis" => match_basis_for(result), "line_item" => result[:line_item], "model" => result[:model], "provider_project_id" => result[:project_id], "provider_api_key_id" => result[:api_key_id], "provider_workspace_id" => result[:organization_id] }.compact end |
.meter_for(result) ⇒ Object
77 78 79 80 81 82 83 84 85 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 77 def meter_for(result) line_item = result[:line_item].to_s.downcase case line_item when /web search/, /search content/ then "web_search" when /file search/ then "file_search_storage" when /code interpreter/, /container/ then "container_session" else DEFAULT_METER end end |
.parse(response, authority: AUTHORITY_COST_API, row_type: ROW_TYPE_COST) ⇒ Object
19 20 21 22 23 24 25 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 19 def parse(response, authority: AUTHORITY_COST_API, row_type: ROW_TYPE_COST) payload = Coercion.coerce_hash(response, label: "OpenAI Costs") buckets = Array(payload[:data]) buckets.flat_map do |bucket| rows_for_bucket(bucket, authority: , row_type: row_type) end.compact end |
.row_for_result(raw, period_start:, period_end:, start_time:, end_time:, authority:, row_type:) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 46 def row_for_result(raw, period_start:, period_end:, start_time:, end_time:, authority:, row_type:) result = Coercion.symbolize(raw) amount = Coercion.symbolize(result[:amount] || {}) billed_amount = amount[:value] return nil if billed_amount.nil? fingerprint = fingerprint_for(result, start_time: start_time, end_time: end_time) { external_id: "cost-#{fingerprint}", period_start: period_start, period_end: period_end, billed_amount: billed_amount, currency: (amount[:currency] || "USD").to_s.upcase, metadata: (result, authority: , row_type: row_type) } end |
.rows_for_bucket(bucket, authority:, row_type:) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb', line 27 def rows_for_bucket(bucket, authority:, row_type:) bucket = Coercion.symbolize(bucket) start_time = bucket[:start_time] end_time = bucket[:end_time] return [] unless start_time && end_time period_start = epoch_to_date(start_time) period_end = end_inclusive_date(end_time) Array(bucket[:results]).filter_map do |raw| row_for_result(raw, period_start: period_start, period_end: period_end, start_time: start_time, end_time: end_time, authority: , row_type: row_type) end rescue ArgumentError [] end |