llm_cost_tracker 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/README.md +11 -5
  4. data/app/assets/llm_cost_tracker/application.css +784 -802
  5. data/app/controllers/llm_cost_tracker/application_controller.rb +14 -2
  6. data/app/controllers/llm_cost_tracker/calls_controller.rb +28 -21
  7. data/app/controllers/llm_cost_tracker/dashboard_controller.rb +1 -4
  8. data/app/controllers/llm_cost_tracker/models_controller.rb +3 -1
  9. data/app/controllers/llm_cost_tracker/pricing_controller.rb +16 -0
  10. data/app/controllers/llm_cost_tracker/tags_controller.rb +3 -1
  11. data/app/helpers/llm_cost_tracker/application_helper.rb +19 -16
  12. data/app/helpers/llm_cost_tracker/chart_helper.rb +22 -6
  13. data/app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb +1 -11
  14. data/app/helpers/llm_cost_tracker/sortable_table_helper.rb +41 -0
  15. data/app/helpers/llm_cost_tracker/token_usage_helper.rb +4 -6
  16. data/app/models/llm_cost_tracker/call.rb +28 -63
  17. data/app/models/llm_cost_tracker/call_line_item.rb +2 -2
  18. data/app/models/llm_cost_tracker/call_rollup.rb +38 -0
  19. data/app/models/llm_cost_tracker/call_tag.rb +0 -2
  20. data/app/models/llm_cost_tracker/ingestion/inbox_entry.rb +2 -0
  21. data/app/services/llm_cost_tracker/dashboard/data_quality.rb +64 -43
  22. data/app/services/llm_cost_tracker/dashboard/filter.rb +5 -0
  23. data/app/services/llm_cost_tracker/dashboard/masking.rb +31 -0
  24. data/app/services/llm_cost_tracker/dashboard/monthly_budget.rb +63 -0
  25. data/app/services/llm_cost_tracker/dashboard/overview_stats.rb +5 -71
  26. data/app/services/llm_cost_tracker/dashboard/pagination.rb +2 -5
  27. data/app/services/llm_cost_tracker/dashboard/pricing_overview.rb +81 -0
  28. data/app/services/llm_cost_tracker/dashboard/setup_state.rb +6 -68
  29. data/app/services/llm_cost_tracker/dashboard/sort.rb +9 -0
  30. data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +20 -12
  31. data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +1 -1
  32. data/app/services/llm_cost_tracker/dashboard/top_models.rb +34 -19
  33. data/app/views/layouts/llm_cost_tracker/application.html.erb +74 -17
  34. data/app/views/llm_cost_tracker/calls/index.html.erb +69 -90
  35. data/app/views/llm_cost_tracker/calls/show.html.erb +132 -125
  36. data/app/views/llm_cost_tracker/dashboard/index.html.erb +120 -159
  37. data/app/views/llm_cost_tracker/data_quality/index.html.erb +140 -194
  38. data/app/views/llm_cost_tracker/errors/database.html.erb +2 -2
  39. data/app/views/llm_cost_tracker/models/index.html.erb +39 -59
  40. data/app/views/llm_cost_tracker/pricing/index.html.erb +93 -0
  41. data/app/views/llm_cost_tracker/shared/_filter_pill_date.html.erb +19 -0
  42. data/app/views/llm_cost_tracker/shared/_filter_pill_model.html.erb +22 -0
  43. data/app/views/llm_cost_tracker/shared/_filter_pill_provider.html.erb +22 -0
  44. data/app/views/llm_cost_tracker/shared/_filter_pill_stream.html.erb +23 -0
  45. data/app/views/llm_cost_tracker/shared/_spend_chart.html.erb +3 -13
  46. data/app/views/llm_cost_tracker/shared/_tag_chips.html.erb +1 -1
  47. data/app/views/llm_cost_tracker/shared/setup_required.html.erb +16 -15
  48. data/app/views/llm_cost_tracker/tags/index.html.erb +27 -32
  49. data/app/views/llm_cost_tracker/tags/show.html.erb +85 -104
  50. data/config/routes.rb +3 -3
  51. data/lib/llm_cost_tracker/budget.rb +25 -28
  52. data/lib/llm_cost_tracker/capture/sdk_payload.rb +34 -0
  53. data/lib/llm_cost_tracker/{parsers → capture}/sse.rb +2 -1
  54. data/lib/llm_cost_tracker/capture/stream_collector.rb +30 -52
  55. data/lib/llm_cost_tracker/capture/stream_tracker.rb +18 -33
  56. data/lib/llm_cost_tracker/capture_verifier.rb +59 -0
  57. data/lib/llm_cost_tracker/charges/cost.rb +27 -0
  58. data/lib/llm_cost_tracker/{billing → charges}/cost_status.rb +14 -4
  59. data/lib/llm_cost_tracker/{billing → charges}/line_item.rb +40 -44
  60. data/lib/llm_cost_tracker/check.rb +5 -0
  61. data/lib/llm_cost_tracker/configuration.rb +13 -61
  62. data/lib/llm_cost_tracker/currency.rb +5 -0
  63. data/lib/llm_cost_tracker/doctor/ingestion_check.rb +15 -49
  64. data/lib/llm_cost_tracker/doctor/price_check.rb +1 -1
  65. data/lib/llm_cost_tracker/doctor/probe.rb +3 -4
  66. data/lib/llm_cost_tracker/doctor/schema_check.rb +3 -6
  67. data/lib/llm_cost_tracker/doctor.rb +66 -64
  68. data/lib/llm_cost_tracker/engine.rb +4 -4
  69. data/lib/llm_cost_tracker/event.rb +12 -20
  70. data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +2 -3
  71. data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +5 -2
  72. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_calls.rb.erb +4 -5
  73. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +3 -2
  74. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_rollups_provider.rb.erb +4 -0
  75. data/lib/llm_cost_tracker/ingestion/batch.rb +39 -8
  76. data/lib/llm_cost_tracker/ingestion/inbox.rb +8 -9
  77. data/lib/llm_cost_tracker/ingestion/pool.rb +3 -11
  78. data/lib/llm_cost_tracker/ingestion/worker.rb +7 -17
  79. data/lib/llm_cost_tracker/ingestion.rb +24 -36
  80. data/lib/llm_cost_tracker/integrations/anthropic.rb +94 -116
  81. data/lib/llm_cost_tracker/integrations/base.rb +39 -57
  82. data/lib/llm_cost_tracker/integrations/openai/batch_capture.rb +84 -0
  83. data/lib/llm_cost_tracker/integrations/openai/patches.rb +81 -0
  84. data/lib/llm_cost_tracker/integrations/openai.rb +72 -332
  85. data/lib/llm_cost_tracker/integrations/ruby_llm.rb +89 -145
  86. data/lib/llm_cost_tracker/integrations.rb +32 -25
  87. data/lib/llm_cost_tracker/ledger/period/totals.rb +27 -42
  88. data/lib/llm_cost_tracker/ledger/period.rb +5 -10
  89. data/lib/llm_cost_tracker/ledger/rollups.rb +67 -98
  90. data/lib/llm_cost_tracker/ledger/schema/adapter.rb +12 -13
  91. data/lib/llm_cost_tracker/ledger/schema/base.rb +51 -0
  92. data/lib/llm_cost_tracker/ledger/schema/call_line_items.rb +24 -79
  93. data/lib/llm_cost_tracker/ledger/schema/call_rollups.rb +3 -35
  94. data/lib/llm_cost_tracker/ledger/schema/call_tags.rb +4 -41
  95. data/lib/llm_cost_tracker/ledger/schema/calls.rb +30 -99
  96. data/lib/llm_cost_tracker/ledger/schema/ingestion/inbox_entries.rb +26 -0
  97. data/lib/llm_cost_tracker/ledger/schema/ingestion/leases.rb +17 -0
  98. data/lib/llm_cost_tracker/ledger/schema.rb +26 -0
  99. data/lib/llm_cost_tracker/ledger/store.rb +18 -42
  100. data/lib/llm_cost_tracker/ledger/tags/{sql.rb → breakdown.rb} +1 -1
  101. data/lib/llm_cost_tracker/ledger/tags/encoding.rb +4 -6
  102. data/lib/llm_cost_tracker/ledger.rb +14 -11
  103. data/lib/llm_cost_tracker/logging.rb +4 -21
  104. data/lib/llm_cost_tracker/middleware/faraday.rb +63 -51
  105. data/lib/llm_cost_tracker/parsers.rb +140 -29
  106. data/lib/llm_cost_tracker/prices.json +1707 -1
  107. data/lib/llm_cost_tracker/pricing/backfill.rb +52 -80
  108. data/lib/llm_cost_tracker/pricing/calculation.rb +260 -0
  109. data/lib/llm_cost_tracker/pricing/effective_prices.rb +17 -18
  110. data/lib/llm_cost_tracker/pricing/estimator.rb +2 -2
  111. data/lib/llm_cost_tracker/pricing/matcher.rb +84 -0
  112. data/lib/llm_cost_tracker/pricing/mode.rb +53 -35
  113. data/lib/llm_cost_tracker/pricing/price_key.rb +56 -0
  114. data/lib/llm_cost_tracker/pricing/rate.rb +18 -0
  115. data/lib/llm_cost_tracker/pricing/registry.rb +189 -100
  116. data/lib/llm_cost_tracker/pricing/service_rates.rb +69 -0
  117. data/lib/llm_cost_tracker/pricing/source.rb +7 -0
  118. data/lib/llm_cost_tracker/pricing/sync/fetcher.rb +2 -3
  119. data/lib/llm_cost_tracker/pricing/sync/registry_diff.rb +4 -10
  120. data/lib/llm_cost_tracker/pricing/sync/registry_writer.rb +10 -3
  121. data/lib/llm_cost_tracker/pricing/sync.rb +9 -11
  122. data/lib/llm_cost_tracker/pricing/unknown.rb +1 -5
  123. data/lib/llm_cost_tracker/pricing.rb +10 -295
  124. data/lib/llm_cost_tracker/providers/anthropic/parser.rb +93 -0
  125. data/lib/llm_cost_tracker/providers/anthropic/response_parser.rb +30 -0
  126. data/lib/llm_cost_tracker/providers/anthropic/usage_extractor.rb +76 -0
  127. data/lib/llm_cost_tracker/providers/azure/hosts.rb +1 -4
  128. data/lib/llm_cost_tracker/providers/azure/parser.rb +44 -0
  129. data/lib/llm_cost_tracker/providers/gemini/model_families.rb +1 -4
  130. data/lib/llm_cost_tracker/providers/gemini/parser.rb +177 -0
  131. data/lib/llm_cost_tracker/providers/gemini/usage_extractor.rb +76 -0
  132. data/lib/llm_cost_tracker/providers/openai/hosts.rb +1 -7
  133. data/lib/llm_cost_tracker/providers/openai/model_families.rb +5 -8
  134. data/lib/llm_cost_tracker/providers/openai/parser.rb +39 -0
  135. data/lib/llm_cost_tracker/providers/openai/response_parser.rb +152 -0
  136. data/lib/llm_cost_tracker/providers/openai/service_charges.rb +181 -0
  137. data/lib/llm_cost_tracker/providers/openai/usage_extractor.rb +72 -0
  138. data/lib/llm_cost_tracker/providers/openai_compatible/parser.rb +36 -0
  139. data/lib/llm_cost_tracker/providers.rb +35 -0
  140. data/lib/llm_cost_tracker/railtie.rb +0 -7
  141. data/lib/llm_cost_tracker/report/data.rb +3 -4
  142. data/lib/llm_cost_tracker/report/formatter.rb +33 -20
  143. data/lib/llm_cost_tracker/report.rb +1 -1
  144. data/lib/llm_cost_tracker/retention.rb +6 -19
  145. data/lib/llm_cost_tracker/tags/context.rb +9 -6
  146. data/lib/llm_cost_tracker/tags/sanitizer.rb +10 -0
  147. data/lib/llm_cost_tracker/timing.rb +2 -4
  148. data/lib/llm_cost_tracker/tracker.rb +24 -36
  149. data/lib/llm_cost_tracker/usage/catalog.rb +58 -0
  150. data/lib/llm_cost_tracker/usage/dimension.rb +21 -0
  151. data/lib/llm_cost_tracker/{billing/components.yml → usage/dimensions.yml} +24 -46
  152. data/lib/llm_cost_tracker/usage/source.rb +14 -0
  153. data/lib/llm_cost_tracker/usage/token_usage.rb +100 -0
  154. data/lib/llm_cost_tracker/version.rb +1 -1
  155. data/lib/llm_cost_tracker.rb +43 -52
  156. data/lib/tasks/llm_cost_tracker.rake +14 -73
  157. metadata +92 -58
  158. data/app/controllers/llm_cost_tracker/reconciliation_controller.rb +0 -106
  159. data/app/helpers/llm_cost_tracker/dashboard_filter_helper.rb +0 -28
  160. data/app/helpers/llm_cost_tracker/reconciliation_helper.rb +0 -13
  161. data/app/models/llm_cost_tracker/provider_invoice.rb +0 -13
  162. data/app/models/llm_cost_tracker/provider_invoice_import.rb +0 -29
  163. data/app/views/llm_cost_tracker/reconciliation/index.html.erb +0 -183
  164. data/app/views/llm_cost_tracker/shared/_active_filters.html.erb +0 -16
  165. data/app/views/llm_cost_tracker/shared/_filters.html.erb +0 -66
  166. data/app/views/llm_cost_tracker/shared/_sort.html.erb +0 -13
  167. data/lib/llm_cost_tracker/billing/components.rb +0 -95
  168. data/lib/llm_cost_tracker/capture/stream.rb +0 -9
  169. data/lib/llm_cost_tracker/doctor/capture_verifier.rb +0 -61
  170. data/lib/llm_cost_tracker/doctor/check.rb +0 -7
  171. data/lib/llm_cost_tracker/doctor/cost_drift_check.rb +0 -56
  172. data/lib/llm_cost_tracker/doctor/invoice_reconciliation_check.rb +0 -164
  173. data/lib/llm_cost_tracker/doctor/legacy_audit_check.rb +0 -34
  174. data/lib/llm_cost_tracker/doctor/legacy_billing_status_check.rb +0 -20
  175. data/lib/llm_cost_tracker/doctor/pricing_snapshot_drift_check.rb +0 -85
  176. data/lib/llm_cost_tracker/generators/llm_cost_tracker/reconciliation_generator.rb +0 -34
  177. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_reconciliation.rb.erb +0 -60
  178. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoice_imports_provider.rb.erb +0 -32
  179. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoices_metadata_index.rb.erb +0 -25
  180. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoice_imports_provider_generator.rb +0 -31
  181. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoices_metadata_index_generator.rb +0 -31
  182. data/lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb +0 -40
  183. data/lib/llm_cost_tracker/ledger/schema/ingestion_inbox_entries.rb +0 -57
  184. data/lib/llm_cost_tracker/ledger/schema/ingestion_leases.rb +0 -52
  185. data/lib/llm_cost_tracker/ledger/schema/provider_invoice_imports.rb +0 -56
  186. data/lib/llm_cost_tracker/ledger/schema/provider_invoices.rb +0 -72
  187. data/lib/llm_cost_tracker/masking.rb +0 -39
  188. data/lib/llm_cost_tracker/parsers/anthropic.rb +0 -193
  189. data/lib/llm_cost_tracker/parsers/azure.rb +0 -46
  190. data/lib/llm_cost_tracker/parsers/base.rb +0 -131
  191. data/lib/llm_cost_tracker/parsers/gemini.rb +0 -232
  192. data/lib/llm_cost_tracker/parsers/openai.rb +0 -41
  193. data/lib/llm_cost_tracker/parsers/openai_compatible.rb +0 -51
  194. data/lib/llm_cost_tracker/parsers/openai_service_charges.rb +0 -155
  195. data/lib/llm_cost_tracker/parsers/openai_usage.rb +0 -228
  196. data/lib/llm_cost_tracker/pricing/explainer.rb +0 -74
  197. data/lib/llm_cost_tracker/pricing/lookup.rb +0 -236
  198. data/lib/llm_cost_tracker/pricing/service_charges.rb +0 -206
  199. data/lib/llm_cost_tracker/providers/anthropic/tier_classification.rb +0 -22
  200. data/lib/llm_cost_tracker/reconcile_tasks.rb +0 -134
  201. data/lib/llm_cost_tracker/reconciliation/diff.rb +0 -409
  202. data/lib/llm_cost_tracker/reconciliation/diff_result.rb +0 -44
  203. data/lib/llm_cost_tracker/reconciliation/import_result.rb +0 -19
  204. data/lib/llm_cost_tracker/reconciliation/importer.rb +0 -254
  205. data/lib/llm_cost_tracker/reconciliation/sources/anthropic_usage.rb +0 -172
  206. data/lib/llm_cost_tracker/reconciliation/sources/fingerprint.rb +0 -20
  207. data/lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb +0 -142
  208. data/lib/llm_cost_tracker/reconciliation.rb +0 -118
  209. data/lib/llm_cost_tracker/token_usage.rb +0 -93
@@ -35,11 +35,9 @@ namespace :llm_cost_tracker do
35
35
  task :verify_capture do
36
36
  Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
37
37
  require_relative "../llm_cost_tracker"
38
- checks = LlmCostTracker::Doctor::CaptureVerifier.call
39
- puts LlmCostTracker::Doctor::CaptureVerifier.report(checks)
40
- unless LlmCostTracker::Doctor::CaptureVerifier.healthy?(checks)
41
- abort("llm_cost_tracker: capture verification failed")
42
- end
38
+ checks = LlmCostTracker::CaptureVerifier.call
39
+ puts LlmCostTracker::CaptureVerifier.report(checks)
40
+ abort("llm_cost_tracker: capture verification failed") unless LlmCostTracker::CaptureVerifier.healthy?(checks)
43
41
  end
44
42
 
45
43
  desc "Print an LLM cost report from ActiveRecord storage"
@@ -66,8 +64,15 @@ namespace :llm_cost_tracker do
66
64
  puts "llm_cost_tracker: pruned #{deleted} calls older than #{days} days"
67
65
  inbox_pruned = LlmCostTracker::Retention.prune_inbox(older_than: days)
68
66
  puts "llm_cost_tracker: pruned #{inbox_pruned} inbox entries older than #{days} days"
69
- invoice_imports_pruned = LlmCostTracker::Retention.prune_invoice_imports(older_than: days)
70
- puts "llm_cost_tracker: pruned #{invoice_imports_pruned} provider invoice imports older than #{days} days"
67
+ end
68
+
69
+ desc "Rebuild llm_cost_tracker_call_rollups from the calls ledger (resync the rollup cache)"
70
+ task rebuild_rollups: :environment do
71
+ unless LlmCostTracker::CallRollup.table_exists?
72
+ abort("llm_cost_tracker: rollups table missing; run the call_rollups generator and migrate first")
73
+ end
74
+ rows = LlmCostTracker::Ledger::Rollups.rebuild!
75
+ puts "llm_cost_tracker: rebuilt #{rows} rollup rows from the calls ledger"
71
76
  end
72
77
 
73
78
  namespace :prices do
@@ -99,7 +104,7 @@ namespace :llm_cost_tracker do
99
104
  puts "llm_cost_tracker: #{action} pricing file #{result.path}"
100
105
  puts " source: #{result.source_url}"
101
106
  puts " version: #{result.source_version.inspect}" if result.source_version
102
- print_changes(result.changes)
107
+ LlmCostTracker::Pricing::Sync::ChangePrinter.call(result.changes)
103
108
  end
104
109
 
105
110
  desc "Compare the current pricing file with the maintained LLM Cost Tracker price snapshot."
@@ -114,80 +119,16 @@ namespace :llm_cost_tracker do
114
119
  puts "llm_cost_tracker: checked pricing file #{result.path}"
115
120
  puts " source: #{result.source_url}"
116
121
  puts " version: #{result.source_version.inspect}" if result.source_version
117
- print_changes(result.changes)
122
+ LlmCostTracker::Pricing::Sync::ChangePrinter.call(result.changes)
118
123
  puts " pricing is up to date" if result.up_to_date
119
124
  abort("llm_cost_tracker: pricing check failed") unless result.up_to_date
120
125
  end
121
-
122
- desc "Explain how a provider/model price is matched. Use PROVIDER=... MODEL=..."
123
- task :explain do
124
- Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
125
- require_relative "../llm_cost_tracker"
126
-
127
- explanation = price_explanation_from_env
128
- puts "llm_cost_tracker: #{explanation.message}"
129
- print_price_explanation(explanation)
130
- abort("llm_cost_tracker: price is incomplete or unknown") unless explanation.complete?
131
- end
132
- end
133
-
134
- namespace :reconcile do
135
- desc "Import provider invoice rows from a JSON INPUT file. " \
136
- "Use SOURCE=openai INPUT=path/to/file.json. Pass PROVIDER=openai for unmapped sources (csv, ...)."
137
- task(:import) { reconcile_run(:run_import) }
138
-
139
- desc "Print a reconciliation diff. " \
140
- "Use SOURCE=openai PERIOD_START=YYYY-MM-DD PERIOD_END=YYYY-MM-DD. PROVIDER=openai for unmapped sources."
141
- task(:diff) { reconcile_run(:run_diff) }
142
126
  end
143
127
  end
144
128
  # rubocop:enable Metrics/BlockLength
145
129
 
146
- def reconcile_run(method)
147
- Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
148
- require_relative "../llm_cost_tracker"
149
- LlmCostTracker::ReconcileTasks.public_send(method, env: ENV)
150
- end
151
-
152
- def print_changes(changes)
153
- LlmCostTracker::Pricing::Sync::ChangePrinter.call(changes)
154
- end
155
-
156
130
  def price_refresh_output_path
157
131
  path = LlmCostTracker::Pricing::Sync.configured_output_path
158
132
  FileUtils.mkdir_p(File.dirname(path))
159
133
  path
160
134
  end
161
-
162
- def price_explanation_from_env
163
- provider = ENV["PROVIDER"].to_s.strip
164
- model = ENV["MODEL"].to_s.strip
165
- abort("llm_cost_tracker: use PROVIDER=... MODEL=...") if provider.empty? || model.empty?
166
-
167
- LlmCostTracker::Pricing.explain(
168
- provider: provider,
169
- model: model,
170
- pricing_mode: ENV.fetch("PRICING_MODE", nil),
171
- tokens: {
172
- input: ENV.fetch("INPUT_TOKENS", 1).to_i,
173
- output: ENV.fetch("OUTPUT_TOKENS", 1).to_i,
174
- cache_read_input: ENV.fetch("CACHE_READ_INPUT_TOKENS", 0).to_i,
175
- cache_write_input: ENV.fetch("CACHE_WRITE_INPUT_TOKENS", 0).to_i,
176
- cache_write_extended_input: ENV.fetch("CACHE_WRITE_EXTENDED_INPUT_TOKENS", 0).to_i,
177
- audio_input: ENV.fetch("AUDIO_INPUT_TOKENS", 0).to_i,
178
- audio_output: ENV.fetch("AUDIO_OUTPUT_TOKENS", 0).to_i
179
- }
180
- )
181
- end
182
-
183
- def print_price_explanation(explanation)
184
- return unless explanation.matched?
185
-
186
- puts " source: #{explanation.source}"
187
- puts " matched_key: #{explanation.matched_key}"
188
- puts " matched_by: #{explanation.matched_by}"
189
- puts " pricing_mode: #{explanation.pricing_mode || 'standard'}"
190
- explanation.effective_prices.each do |key, value|
191
- puts " #{key}: #{value.nil? ? 'missing' : value}"
192
- end
193
- end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_cost_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Khomenko
@@ -103,6 +103,20 @@ dependencies:
103
103
  - - "<"
104
104
  - !ruby/object:Gem::Version
105
105
  version: '9.0'
106
+ - !ruby/object:Gem::Dependency
107
+ name: anthropic
108
+ requirement: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '1.42'
113
+ type: :development
114
+ prerelease: false
115
+ version_requirements: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '1.42'
106
120
  - !ruby/object:Gem::Dependency
107
121
  name: nokogiri
108
122
  requirement: !ruby/object:Gem::Requirement
@@ -117,6 +131,20 @@ dependencies:
117
131
  - - "~>"
118
132
  - !ruby/object:Gem::Version
119
133
  version: '1.16'
134
+ - !ruby/object:Gem::Dependency
135
+ name: openai
136
+ requirement: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - "~>"
139
+ - !ruby/object:Gem::Version
140
+ version: '0.63'
141
+ type: :development
142
+ prerelease: false
143
+ version_requirements: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - "~>"
146
+ - !ruby/object:Gem::Version
147
+ version: '0.63'
120
148
  - !ruby/object:Gem::Dependency
121
149
  name: pg
122
150
  requirement: !ruby/object:Gem::Requirement
@@ -173,6 +201,20 @@ dependencies:
173
201
  - - "~>"
174
202
  - !ruby/object:Gem::Version
175
203
  version: '1.0'
204
+ - !ruby/object:Gem::Dependency
205
+ name: ruby_llm
206
+ requirement: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - "~>"
209
+ - !ruby/object:Gem::Version
210
+ version: '1.15'
211
+ type: :development
212
+ prerelease: false
213
+ version_requirements: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - "~>"
216
+ - !ruby/object:Gem::Version
217
+ version: '1.15'
176
218
  - !ruby/object:Gem::Dependency
177
219
  name: simplecov
178
220
  requirement: !ruby/object:Gem::Requirement
@@ -239,16 +281,15 @@ files:
239
281
  - app/controllers/llm_cost_tracker/dashboard_controller.rb
240
282
  - app/controllers/llm_cost_tracker/data_quality_controller.rb
241
283
  - app/controllers/llm_cost_tracker/models_controller.rb
242
- - app/controllers/llm_cost_tracker/reconciliation_controller.rb
284
+ - app/controllers/llm_cost_tracker/pricing_controller.rb
243
285
  - app/controllers/llm_cost_tracker/tags_controller.rb
244
286
  - app/helpers/llm_cost_tracker/application_helper.rb
245
287
  - app/helpers/llm_cost_tracker/chart_helper.rb
246
- - app/helpers/llm_cost_tracker/dashboard_filter_helper.rb
247
288
  - app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb
248
289
  - app/helpers/llm_cost_tracker/dashboard_query_helper.rb
249
290
  - app/helpers/llm_cost_tracker/inline_style_helper.rb
250
291
  - app/helpers/llm_cost_tracker/pagination_helper.rb
251
- - app/helpers/llm_cost_tracker/reconciliation_helper.rb
292
+ - app/helpers/llm_cost_tracker/sortable_table_helper.rb
252
293
  - app/helpers/llm_cost_tracker/token_usage_helper.rb
253
294
  - app/models/llm_cost_tracker/call.rb
254
295
  - app/models/llm_cost_tracker/call_line_item.rb
@@ -256,16 +297,18 @@ files:
256
297
  - app/models/llm_cost_tracker/call_tag.rb
257
298
  - app/models/llm_cost_tracker/ingestion/inbox_entry.rb
258
299
  - app/models/llm_cost_tracker/ingestion/lease.rb
259
- - app/models/llm_cost_tracker/provider_invoice.rb
260
- - app/models/llm_cost_tracker/provider_invoice_import.rb
261
300
  - app/services/llm_cost_tracker/dashboard/data_quality.rb
262
301
  - app/services/llm_cost_tracker/dashboard/date_range.rb
263
302
  - app/services/llm_cost_tracker/dashboard/filter.rb
303
+ - app/services/llm_cost_tracker/dashboard/masking.rb
304
+ - app/services/llm_cost_tracker/dashboard/monthly_budget.rb
264
305
  - app/services/llm_cost_tracker/dashboard/overview_stats.rb
265
306
  - app/services/llm_cost_tracker/dashboard/pagination.rb
266
307
  - app/services/llm_cost_tracker/dashboard/params.rb
308
+ - app/services/llm_cost_tracker/dashboard/pricing_overview.rb
267
309
  - app/services/llm_cost_tracker/dashboard/provider_breakdown.rb
268
310
  - app/services/llm_cost_tracker/dashboard/setup_state.rb
311
+ - app/services/llm_cost_tracker/dashboard/sort.rb
269
312
  - app/services/llm_cost_tracker/dashboard/spend_anomaly.rb
270
313
  - app/services/llm_cost_tracker/dashboard/tag_breakdown.rb
271
314
  - app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb
@@ -280,12 +323,13 @@ files:
280
323
  - app/views/llm_cost_tracker/errors/invalid_filter.html.erb
281
324
  - app/views/llm_cost_tracker/errors/not_found.html.erb
282
325
  - app/views/llm_cost_tracker/models/index.html.erb
283
- - app/views/llm_cost_tracker/reconciliation/index.html.erb
284
- - app/views/llm_cost_tracker/shared/_active_filters.html.erb
326
+ - app/views/llm_cost_tracker/pricing/index.html.erb
285
327
  - app/views/llm_cost_tracker/shared/_bar.html.erb
286
- - app/views/llm_cost_tracker/shared/_filters.html.erb
328
+ - app/views/llm_cost_tracker/shared/_filter_pill_date.html.erb
329
+ - app/views/llm_cost_tracker/shared/_filter_pill_model.html.erb
330
+ - app/views/llm_cost_tracker/shared/_filter_pill_provider.html.erb
331
+ - app/views/llm_cost_tracker/shared/_filter_pill_stream.html.erb
287
332
  - app/views/llm_cost_tracker/shared/_metric_stack.html.erb
288
- - app/views/llm_cost_tracker/shared/_sort.html.erb
289
333
  - app/views/llm_cost_tracker/shared/_spend_chart.html.erb
290
334
  - app/views/llm_cost_tracker/shared/_tag_chips.html.erb
291
335
  - app/views/llm_cost_tracker/shared/setup_required.html.erb
@@ -294,25 +338,21 @@ files:
294
338
  - config/routes.rb
295
339
  - lib/llm_cost_tracker.rb
296
340
  - lib/llm_cost_tracker/assets.rb
297
- - lib/llm_cost_tracker/billing/components.rb
298
- - lib/llm_cost_tracker/billing/components.yml
299
- - lib/llm_cost_tracker/billing/cost_status.rb
300
- - lib/llm_cost_tracker/billing/line_item.rb
301
341
  - lib/llm_cost_tracker/budget.rb
302
- - lib/llm_cost_tracker/capture/stream.rb
342
+ - lib/llm_cost_tracker/capture/sdk_payload.rb
343
+ - lib/llm_cost_tracker/capture/sse.rb
303
344
  - lib/llm_cost_tracker/capture/stream_collector.rb
304
345
  - lib/llm_cost_tracker/capture/stream_tracker.rb
346
+ - lib/llm_cost_tracker/capture_verifier.rb
347
+ - lib/llm_cost_tracker/charges/cost.rb
348
+ - lib/llm_cost_tracker/charges/cost_status.rb
349
+ - lib/llm_cost_tracker/charges/line_item.rb
350
+ - lib/llm_cost_tracker/check.rb
305
351
  - lib/llm_cost_tracker/configuration.rb
352
+ - lib/llm_cost_tracker/currency.rb
306
353
  - lib/llm_cost_tracker/doctor.rb
307
- - lib/llm_cost_tracker/doctor/capture_verifier.rb
308
- - lib/llm_cost_tracker/doctor/check.rb
309
- - lib/llm_cost_tracker/doctor/cost_drift_check.rb
310
354
  - lib/llm_cost_tracker/doctor/ingestion_check.rb
311
- - lib/llm_cost_tracker/doctor/invoice_reconciliation_check.rb
312
- - lib/llm_cost_tracker/doctor/legacy_audit_check.rb
313
- - lib/llm_cost_tracker/doctor/legacy_billing_status_check.rb
314
355
  - lib/llm_cost_tracker/doctor/price_check.rb
315
- - lib/llm_cost_tracker/doctor/pricing_snapshot_drift_check.rb
316
356
  - lib/llm_cost_tracker/doctor/probe.rb
317
357
  - lib/llm_cost_tracker/doctor/schema_check.rb
318
358
  - lib/llm_cost_tracker/engine.rb
@@ -322,22 +362,16 @@ files:
322
362
  - lib/llm_cost_tracker/generators/llm_cost_tracker/call_rollups_generator.rb
323
363
  - lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb
324
364
  - lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb
325
- - lib/llm_cost_tracker/generators/llm_cost_tracker/reconciliation_generator.rb
326
365
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_async_ingestion.rb.erb
327
366
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_call_rollups.rb.erb
328
367
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_calls.rb.erb
329
- - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_reconciliation.rb.erb
330
368
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb
331
369
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_rollups_provider.rb.erb
332
370
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_tags_key_value_index.rb.erb
333
371
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_image_tokens.rb.erb
334
- - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoice_imports_provider.rb.erb
335
- - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoices_metadata_index.rb.erb
336
372
  - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_call_rollups_provider_generator.rb
337
373
  - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_call_tags_key_value_index_generator.rb
338
374
  - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_image_tokens_generator.rb
339
- - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoice_imports_provider_generator.rb
340
- - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoices_metadata_index_generator.rb
341
375
  - lib/llm_cost_tracker/ingestion.rb
342
376
  - lib/llm_cost_tracker/ingestion/batch.rb
343
377
  - lib/llm_cost_tracker/ingestion/inbox.rb
@@ -348,69 +382,65 @@ files:
348
382
  - lib/llm_cost_tracker/integrations/anthropic.rb
349
383
  - lib/llm_cost_tracker/integrations/base.rb
350
384
  - lib/llm_cost_tracker/integrations/openai.rb
385
+ - lib/llm_cost_tracker/integrations/openai/batch_capture.rb
386
+ - lib/llm_cost_tracker/integrations/openai/patches.rb
351
387
  - lib/llm_cost_tracker/integrations/ruby_llm.rb
352
388
  - lib/llm_cost_tracker/ledger.rb
353
389
  - lib/llm_cost_tracker/ledger/period.rb
354
390
  - lib/llm_cost_tracker/ledger/period/totals.rb
355
391
  - lib/llm_cost_tracker/ledger/rollups.rb
356
- - lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb
392
+ - lib/llm_cost_tracker/ledger/schema.rb
357
393
  - lib/llm_cost_tracker/ledger/schema/adapter.rb
394
+ - lib/llm_cost_tracker/ledger/schema/base.rb
358
395
  - lib/llm_cost_tracker/ledger/schema/call_line_items.rb
359
396
  - lib/llm_cost_tracker/ledger/schema/call_rollups.rb
360
397
  - lib/llm_cost_tracker/ledger/schema/call_tags.rb
361
398
  - lib/llm_cost_tracker/ledger/schema/calls.rb
362
- - lib/llm_cost_tracker/ledger/schema/ingestion_inbox_entries.rb
363
- - lib/llm_cost_tracker/ledger/schema/ingestion_leases.rb
364
- - lib/llm_cost_tracker/ledger/schema/provider_invoice_imports.rb
365
- - lib/llm_cost_tracker/ledger/schema/provider_invoices.rb
399
+ - lib/llm_cost_tracker/ledger/schema/ingestion/inbox_entries.rb
400
+ - lib/llm_cost_tracker/ledger/schema/ingestion/leases.rb
366
401
  - lib/llm_cost_tracker/ledger/store.rb
402
+ - lib/llm_cost_tracker/ledger/tags/breakdown.rb
367
403
  - lib/llm_cost_tracker/ledger/tags/encoding.rb
368
404
  - lib/llm_cost_tracker/ledger/tags/query.rb
369
- - lib/llm_cost_tracker/ledger/tags/sql.rb
370
405
  - lib/llm_cost_tracker/logging.rb
371
- - lib/llm_cost_tracker/masking.rb
372
406
  - lib/llm_cost_tracker/middleware/faraday.rb
373
407
  - lib/llm_cost_tracker/parsers.rb
374
- - lib/llm_cost_tracker/parsers/anthropic.rb
375
- - lib/llm_cost_tracker/parsers/azure.rb
376
- - lib/llm_cost_tracker/parsers/base.rb
377
- - lib/llm_cost_tracker/parsers/gemini.rb
378
- - lib/llm_cost_tracker/parsers/openai.rb
379
- - lib/llm_cost_tracker/parsers/openai_compatible.rb
380
- - lib/llm_cost_tracker/parsers/openai_service_charges.rb
381
- - lib/llm_cost_tracker/parsers/openai_usage.rb
382
- - lib/llm_cost_tracker/parsers/sse.rb
383
408
  - lib/llm_cost_tracker/prices.json
384
409
  - lib/llm_cost_tracker/pricing.rb
385
410
  - lib/llm_cost_tracker/pricing/backfill.rb
411
+ - lib/llm_cost_tracker/pricing/calculation.rb
386
412
  - lib/llm_cost_tracker/pricing/effective_prices.rb
387
413
  - lib/llm_cost_tracker/pricing/estimator.rb
388
- - lib/llm_cost_tracker/pricing/explainer.rb
389
- - lib/llm_cost_tracker/pricing/lookup.rb
414
+ - lib/llm_cost_tracker/pricing/matcher.rb
390
415
  - lib/llm_cost_tracker/pricing/mode.rb
416
+ - lib/llm_cost_tracker/pricing/price_key.rb
417
+ - lib/llm_cost_tracker/pricing/rate.rb
391
418
  - lib/llm_cost_tracker/pricing/registry.rb
392
- - lib/llm_cost_tracker/pricing/service_charges.rb
419
+ - lib/llm_cost_tracker/pricing/service_rates.rb
420
+ - lib/llm_cost_tracker/pricing/source.rb
393
421
  - lib/llm_cost_tracker/pricing/sync.rb
394
422
  - lib/llm_cost_tracker/pricing/sync/change_printer.rb
395
423
  - lib/llm_cost_tracker/pricing/sync/fetcher.rb
396
424
  - lib/llm_cost_tracker/pricing/sync/registry_diff.rb
397
425
  - lib/llm_cost_tracker/pricing/sync/registry_writer.rb
398
426
  - lib/llm_cost_tracker/pricing/unknown.rb
399
- - lib/llm_cost_tracker/providers/anthropic/tier_classification.rb
427
+ - lib/llm_cost_tracker/providers.rb
428
+ - lib/llm_cost_tracker/providers/anthropic/parser.rb
429
+ - lib/llm_cost_tracker/providers/anthropic/response_parser.rb
430
+ - lib/llm_cost_tracker/providers/anthropic/usage_extractor.rb
400
431
  - lib/llm_cost_tracker/providers/azure/hosts.rb
432
+ - lib/llm_cost_tracker/providers/azure/parser.rb
401
433
  - lib/llm_cost_tracker/providers/gemini/model_families.rb
434
+ - lib/llm_cost_tracker/providers/gemini/parser.rb
435
+ - lib/llm_cost_tracker/providers/gemini/usage_extractor.rb
402
436
  - lib/llm_cost_tracker/providers/openai/hosts.rb
403
437
  - lib/llm_cost_tracker/providers/openai/model_families.rb
438
+ - lib/llm_cost_tracker/providers/openai/parser.rb
439
+ - lib/llm_cost_tracker/providers/openai/response_parser.rb
440
+ - lib/llm_cost_tracker/providers/openai/service_charges.rb
441
+ - lib/llm_cost_tracker/providers/openai/usage_extractor.rb
442
+ - lib/llm_cost_tracker/providers/openai_compatible/parser.rb
404
443
  - lib/llm_cost_tracker/railtie.rb
405
- - lib/llm_cost_tracker/reconcile_tasks.rb
406
- - lib/llm_cost_tracker/reconciliation.rb
407
- - lib/llm_cost_tracker/reconciliation/diff.rb
408
- - lib/llm_cost_tracker/reconciliation/diff_result.rb
409
- - lib/llm_cost_tracker/reconciliation/import_result.rb
410
- - lib/llm_cost_tracker/reconciliation/importer.rb
411
- - lib/llm_cost_tracker/reconciliation/sources/anthropic_usage.rb
412
- - lib/llm_cost_tracker/reconciliation/sources/fingerprint.rb
413
- - lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb
414
444
  - lib/llm_cost_tracker/report.rb
415
445
  - lib/llm_cost_tracker/report/data.rb
416
446
  - lib/llm_cost_tracker/report/formatter.rb
@@ -419,8 +449,12 @@ files:
419
449
  - lib/llm_cost_tracker/tags/key.rb
420
450
  - lib/llm_cost_tracker/tags/sanitizer.rb
421
451
  - lib/llm_cost_tracker/timing.rb
422
- - lib/llm_cost_tracker/token_usage.rb
423
452
  - lib/llm_cost_tracker/tracker.rb
453
+ - lib/llm_cost_tracker/usage/catalog.rb
454
+ - lib/llm_cost_tracker/usage/dimension.rb
455
+ - lib/llm_cost_tracker/usage/dimensions.yml
456
+ - lib/llm_cost_tracker/usage/source.rb
457
+ - lib/llm_cost_tracker/usage/token_usage.rb
424
458
  - lib/llm_cost_tracker/version.rb
425
459
  - lib/tasks/llm_cost_tracker.rake
426
460
  homepage: https://github.com/sergey-homenko/llm_cost_tracker
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- class ReconciliationController < ApplicationController
5
- def index
6
- @reconciliation_enabled = LlmCostTracker::Reconciliation.enabled?
7
- @reconciliation_installed = LlmCostTracker::ProviderInvoice.table_exists?
8
- if @reconciliation_enabled && @reconciliation_installed
9
- @scopes = invoice_scopes
10
- @sources = @scopes.map { |scope| scope[:source] }.uniq
11
- @diffs = @scopes.filter_map { |scope| diff_for(scope) }
12
- @last_imported_at = LlmCostTracker::ProviderInvoice.maximum(:imported_at)
13
- else
14
- @scopes = []
15
- @sources = []
16
- @diffs = []
17
- @last_imported_at = nil
18
- end
19
- @threshold = LlmCostTracker::Reconciliation::DEFAULT_THRESHOLD_PERCENT
20
- @configured_importers = @reconciliation_enabled ? configured_importers : {}
21
- end
22
-
23
- def trigger_import
24
- unless LlmCostTracker::Reconciliation.enabled?
25
- return redirect_to reconciliation_path, alert: "Reconciliation is disabled"
26
- end
27
-
28
- source = params[:source].to_s
29
- importer = configured_importers[source.to_sym]
30
- return redirect_to reconciliation_path, alert: "No importer configured for #{source}" if importer.nil?
31
-
32
- result = importer.call
33
- if result.respond_to?(:errors) && result.errors.any?
34
- LlmCostTracker::Logging.warn(
35
- "Reconciliation import for #{source} returned #{result.errors.size} row error(s)"
36
- )
37
- return redirect_to(
38
- reconciliation_path,
39
- alert: "Imported #{result.respond_to?(:total_imported) ? result.total_imported : 0} " \
40
- "#{source} rows with #{result.errors.size} row error(s); see Rails logs for details."
41
- )
42
- end
43
- message = if result.respond_to?(:total_imported)
44
- "Imported #{result.total_imported} #{source} rows"
45
- else
46
- "Triggered #{source} importer"
47
- end
48
- redirect_to reconciliation_path, notice: message
49
- rescue StandardError => e
50
- LlmCostTracker::Logging.warn("Reconciliation import failed for #{source}: #{e.class}: #{e.message}")
51
- redirect_to reconciliation_path,
52
- alert: "Import failed (#{e.class.name}); see Rails logs for details."
53
- end
54
-
55
- private
56
-
57
- def configured_importers
58
- LlmCostTracker.configuration.reconciliation_importers
59
- end
60
-
61
- def invoice_scopes
62
- connection = LlmCostTracker::ProviderInvoice.connection
63
- provider_expr =
64
- if LlmCostTracker::Ledger::Schema::Adapter.postgresql?(connection)
65
- Arel.sql("metadata->>'provider'")
66
- else
67
- Arel.sql("JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.provider'))")
68
- end
69
- LlmCostTracker::ProviderInvoice
70
- .group(:source, provider_expr, :currency)
71
- .order(:source, :currency)
72
- .pluck(:source, provider_expr, :currency)
73
- .map { |source, provider, currency| { source: source, provider: provider, currency: currency.upcase } }
74
- end
75
-
76
- def diff_for(scope)
77
- window = scope_invoices(scope)
78
- .order(period_end: :desc, period_start: :desc)
79
- .limit(1)
80
- .pick(:period_start, :period_end)
81
- return nil unless window
82
-
83
- LlmCostTracker::Reconciliation.diff(
84
- source: scope[:source], provider: scope[:provider], currency: scope[:currency],
85
- period_start: window[0], period_end: window[1]
86
- )
87
- rescue ArgumentError => e
88
- LlmCostTracker::Logging.warn("Reconciliation diff skipped for #{scope.inspect}: #{e.message}")
89
- nil
90
- end
91
-
92
- def scope_invoices(scope)
93
- relation = LlmCostTracker::ProviderInvoice
94
- .where(source: scope[:source], currency: scope[:currency])
95
- connection = LlmCostTracker::ProviderInvoice.connection
96
- provider = scope[:provider]
97
- return relation if provider.nil? || provider.empty?
98
-
99
- if LlmCostTracker::Ledger::Schema::Adapter.postgresql?(connection)
100
- relation.where("metadata->>'provider' = ?", provider)
101
- else
102
- relation.where("JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.provider')) = ?", provider)
103
- end
104
- end
105
- end
106
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- module DashboardFilterHelper
5
- FILTER_PARAM_KEYS = %i[from to provider model stream usage_source tag].freeze
6
-
7
- STREAM_FILTER_OPTIONS = [
8
- ["Streaming only", "yes"],
9
- ["Non-streaming only", "no"]
10
- ].freeze
11
-
12
- def any_filter_applied?
13
- FILTER_PARAM_KEYS.any? { |key| params[key].present? }
14
- end
15
-
16
- def active_tag_filters
17
- tag_params = LlmCostTracker::Dashboard::Params.tag_query(params[:tag])
18
-
19
- tag_params.filter_map do |key, value|
20
- {
21
- label: "Tag",
22
- value: "#{key}=#{value}",
23
- path: dashboard_filter_path(current_query(tag: tag_params.except(key).presence, page: nil))
24
- }
25
- end
26
- end
27
- end
28
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- module ReconciliationHelper
5
- def attribution_summary(attribution)
6
- LlmCostTracker::Masking.format_attribution(attribution)
7
- end
8
-
9
- def mask_secret(value)
10
- LlmCostTracker::Masking.mask_value(:provider_api_key_id, value)
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- class ProviderInvoice < ActiveRecord::Base
5
- before_validation :normalize_currency
6
-
7
- private
8
-
9
- def normalize_currency
10
- self.currency = currency.to_s.upcase if currency.present?
11
- end
12
- end
13
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- class ProviderInvoiceImport < ActiveRecord::Base
5
- STATE_RUNNING = "running"
6
- STATE_COMPLETED = "completed"
7
- STATE_FAILED = "failed"
8
- STATES = [STATE_RUNNING, STATE_COMPLETED, STATE_FAILED].freeze
9
-
10
- scope :for_source, ->(source) { where(source: source.to_s) }
11
- scope :for_provider, ->(provider) { where(provider: provider.to_s) }
12
- scope :running, -> { where(state: STATE_RUNNING) }
13
- scope :completed, -> { where(state: STATE_COMPLETED) }
14
- scope :failed, -> { where(state: STATE_FAILED) }
15
- scope :latest, -> { order(started_at: :desc, id: :desc) }
16
-
17
- def self.resume_cursor_for(source, provider: nil)
18
- scope = for_source(source)
19
- scope = scope.for_provider(provider) if provider
20
- scope.latest.limit(1).pick(:cursor)
21
- end
22
-
23
- def self.last_completed_window_for(source, provider: nil)
24
- scope = for_source(source)
25
- scope = scope.for_provider(provider) if provider
26
- scope.completed.latest.limit(1).pick(:window_start, :window_end)
27
- end
28
- end
29
- end