Module: LlmCostTracker::ReconcileTasks
- Defined in:
- lib/llm_cost_tracker/reconcile_tasks.rb
Constant Summary collapse
- SOURCE_PARSERS =
{ "openai" => Reconciliation::Sources::OpenaiUsage, "anthropic" => Reconciliation::Sources::AnthropicUsage }.freeze
- GENERIC_SOURCES =
%w[csv].freeze
Class Method Summary collapse
- .diff_from_env(env: ENV) ⇒ Object
- .format_amount(value) ⇒ Object
- .format_attribution(attribution) ⇒ Object
- .import_from_env(env: ENV) ⇒ Object
- .parse_drilldown_limit(value) ⇒ Object
- .parse_rows(source:, payload:) ⇒ Object
- .print_diff(diff, output: $stdout) ⇒ Object
- .print_non_cost_rows(diff, output) ⇒ Object
- .print_section(output, label, rows, total) ⇒ Object
- .print_unmatched_local_calls(diff, output) ⇒ Object
- .print_unmatched_provider_rows(diff, output) ⇒ Object
- .required_env(env, key) ⇒ Object
- .run_diff(env: ENV, output: $stdout) ⇒ Object
- .run_import(env: ENV, output: $stdout, error_output: $stderr) ⇒ Object
- .truncation_suffix(shown, total) ⇒ Object
Class Method Details
.diff_from_env(env: ENV) ⇒ Object
43 44 45 46 47 48 49 50 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 43 def diff_from_env(env: ENV) source = required_env(env, "SOURCE") period_start = Date.parse(required_env(env, "PERIOD_START")) period_end = Date.parse(required_env(env, "PERIOD_END")) Reconciliation.diff(source: source.to_sym, period_start: period_start, period_end: period_end, provider: env["PROVIDER"], drilldown_limit: parse_drilldown_limit(env["DRILLDOWN_LIMIT"])) end |
.format_amount(value) ⇒ Object
123 124 125 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 123 def format_amount(value) value.nil? ? "n/a" : value.to_s("F") end |
.format_attribution(attribution) ⇒ Object
127 128 129 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 127 def format_attribution(attribution) LlmCostTracker::Masking.format_attribution(attribution, separator: ",") end |
.import_from_env(env: ENV) ⇒ Object
33 34 35 36 37 38 39 40 41 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 33 def import_from_env(env: ENV) source = required_env(env, "SOURCE") input_path = required_env(env, "INPUT") raise ArgumentError, "INPUT file not found: #{input_path}" unless File.exist?(input_path) payload = JSON.parse(File.read(input_path)) rows = parse_rows(source: source, payload: payload) Reconciliation.import(source: source.to_sym, rows: rows, provider: env["PROVIDER"]) end |
.parse_drilldown_limit(value) ⇒ Object
52 53 54 55 56 57 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 52 def parse_drilldown_limit(value) return Reconciliation::Diff::DEFAULT_DRILLDOWN_LIMIT if value.nil? || value.to_s.empty? return nil if value.to_s.downcase == "all" Integer(value) end |
.parse_rows(source:, payload:) ⇒ Object
71 72 73 74 75 76 77 78 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 71 def parse_rows(source:, payload:) parser = SOURCE_PARSERS[source.to_s] return parser.parse(payload) if parser return Array(payload["rows"]) if GENERIC_SOURCES.include?(source.to_s) known = (SOURCE_PARSERS.keys + GENERIC_SOURCES).join(", ") raise ArgumentError, "unknown SOURCE #{source.inspect}; known sources: #{known}" end |
.print_diff(diff, output: $stdout) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 59 def print_diff(diff, output: $stdout) output.puts "llm_cost_tracker: reconciliation diff for #{diff.source} " \ "#{diff.period_start}..#{diff.period_end}" output.puts " provider_total: #{diff.provider_total.to_s('F')} #{diff.currency}" output.puts " local_total: #{diff.local_total.to_s('F')} #{diff.currency} " \ "(from #{diff.local_total_source})" output.puts " delta: #{diff.delta_amount.to_s('F')} (#{diff.delta_percent || 'n/a'}%)" print_unmatched_provider_rows(diff, output) print_unmatched_local_calls(diff, output) print_non_cost_rows(diff, output) end |
.print_non_cost_rows(diff, output) ⇒ Object
102 103 104 105 106 107 108 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 102 def print_non_cost_rows(diff, output) print_section(output, "non-cost evidence", diff.non_cost_rows, diff.non_cost_rows_total) do |row| "[#{row[:row_type]}/#{row[:meter]}] " \ "#{format_amount(row[:billed_amount])} #{format_attribution(row[:attribution])}" end end |
.print_section(output, label, rows, total) ⇒ Object
110 111 112 113 114 115 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 110 def print_section(output, label, rows, total) return if rows.empty? output.puts " #{label}#{truncation_suffix(rows.size, total)}:" rows.each { |row| output.puts " #{yield(row)}" } end |
.print_unmatched_local_calls(diff, output) ⇒ Object
95 96 97 98 99 100 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 95 def print_unmatched_local_calls(diff, output) print_section(output, "unmatched local calls", diff.unmatched_local_calls, diff.unmatched_local_calls_total) do |row| "#{row[:count]} calls / #{row[:total_cost].to_s('F')} #{format_attribution(row[:attribution])}" end end |
.print_unmatched_provider_rows(diff, output) ⇒ Object
87 88 89 90 91 92 93 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 87 def print_unmatched_provider_rows(diff, output) print_section(output, "unmatched provider rows", diff.unmatched_provider_rows, diff.unmatched_provider_rows_total) do |row| "#{row[:external_id]} (#{row[:match_basis]}): " \ "#{format_amount(row[:billed_amount])} #{format_attribution(row[:attribution])}" end end |
.required_env(env, key) ⇒ Object
80 81 82 83 84 85 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 80 def required_env(env, key) value = env[key].to_s.strip raise ArgumentError, "missing #{key}" if value.empty? value end |
.run_diff(env: ENV, output: $stdout) ⇒ Object
27 28 29 30 31 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 27 def run_diff(env: ENV, output: $stdout) diff = diff_from_env(env: env) print_diff(diff, output: output) diff end |
.run_import(env: ENV, output: $stdout, error_output: $stderr) ⇒ Object
17 18 19 20 21 22 23 24 25 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 17 def run_import(env: ENV, output: $stdout, error_output: $stderr) result = import_from_env(env: env) output.puts "llm_cost_tracker: imported #{result.total_imported} rows " \ "(inserted=#{result.inserted}, updated=#{result.updated}, skipped=#{result.skipped})" result.errors.each { |error| error_output.puts " error: #{error}" } raise "llm_cost_tracker: reconcile import had errors" unless result.success? result end |
.truncation_suffix(shown, total) ⇒ Object
117 118 119 120 121 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 117 def truncation_suffix(shown, total) return "" if shown >= total " (showing #{shown} of #{total} — pass DRILLDOWN_LIMIT=all to see every row)" end |