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_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
126 127 128 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 126 def format_amount(value) value.nil? ? "n/a" : value.to_s("F") end |
.format_attribution(attribution) ⇒ Object
130 131 132 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 130 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
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 109 def print_non_cost_rows(diff, output) return if diff.non_cost_rows.empty? output.puts " non-cost evidence#{truncation_suffix(diff.non_cost_rows.size, diff.non_cost_rows_total)}:" diff.non_cost_rows.each do |row| output.puts " [#{row[:row_type]}/#{row[:meter]}] #{format_amount(row[:billed_amount])} " \ "#{format_attribution(row[:attribution])}" end end |
.print_unmatched_local_calls(diff, output) ⇒ Object
98 99 100 101 102 103 104 105 106 107 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 98 def print_unmatched_local_calls(diff, output) return if diff.unmatched_local_calls.empty? output.puts " unmatched local calls#{truncation_suffix(diff.unmatched_local_calls.size, diff.unmatched_local_calls_total)}:" diff.unmatched_local_calls.each do |row| output.puts " #{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 94 95 96 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 87 def print_unmatched_provider_rows(diff, output) return if diff.unmatched_provider_rows.empty? output.puts " unmatched provider rows#{truncation_suffix(diff.unmatched_provider_rows.size, diff.unmatched_provider_rows_total)}:" diff.unmatched_provider_rows.each do |row| output.puts " #{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
120 121 122 123 124 |
# File 'lib/llm_cost_tracker/reconcile_tasks.rb', line 120 def truncation_suffix(shown, total) return "" if shown >= total " (showing #{shown} of #{total} — pass DRILLDOWN_LIMIT=all to see every row)" end |