llm_cost_tracker 0.11.0 → 0.13.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/README.md +7 -4
- data/app/assets/llm_cost_tracker/application.css +8 -7
- data/app/controllers/llm_cost_tracker/calls_controller.rb +5 -5
- data/app/controllers/llm_cost_tracker/dashboard_controller.rb +1 -1
- data/app/controllers/llm_cost_tracker/data_quality_controller.rb +1 -0
- data/app/controllers/llm_cost_tracker/pricing_controller.rb +1 -1
- data/app/helpers/llm_cost_tracker/application_helper.rb +12 -19
- data/app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb +1 -11
- data/app/helpers/llm_cost_tracker/sortable_table_helper.rb +4 -4
- data/app/helpers/llm_cost_tracker/token_usage_helper.rb +4 -6
- data/app/models/llm_cost_tracker/call.rb +28 -63
- data/app/models/llm_cost_tracker/call_line_item.rb +2 -2
- data/app/models/llm_cost_tracker/call_rollup.rb +38 -0
- data/app/models/llm_cost_tracker/call_tag.rb +0 -2
- data/app/models/llm_cost_tracker/ingestion/inbox_entry.rb +3 -0
- data/app/services/llm_cost_tracker/dashboard/data_quality.rb +76 -43
- data/app/services/llm_cost_tracker/dashboard/filter.rb +5 -0
- data/app/services/llm_cost_tracker/dashboard/masking.rb +31 -0
- data/app/services/llm_cost_tracker/dashboard/monthly_budget.rb +63 -0
- data/app/services/llm_cost_tracker/dashboard/overview_stats.rb +5 -71
- data/app/services/llm_cost_tracker/dashboard/pagination.rb +2 -5
- data/app/services/llm_cost_tracker/dashboard/pricing_overview.rb +30 -44
- data/app/services/llm_cost_tracker/dashboard/setup_state.rb +4 -60
- data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +1 -7
- data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +1 -1
- data/app/views/layouts/llm_cost_tracker/application.html.erb +0 -6
- data/app/views/llm_cost_tracker/calls/index.html.erb +8 -8
- data/app/views/llm_cost_tracker/calls/show.html.erb +33 -25
- data/app/views/llm_cost_tracker/dashboard/index.html.erb +8 -8
- data/app/views/llm_cost_tracker/data_quality/index.html.erb +73 -117
- data/app/views/llm_cost_tracker/models/index.html.erb +5 -5
- data/app/views/llm_cost_tracker/pricing/index.html.erb +3 -3
- data/app/views/llm_cost_tracker/shared/_filter_pill_model.html.erb +1 -1
- data/app/views/llm_cost_tracker/shared/_filter_pill_provider.html.erb +1 -1
- data/app/views/llm_cost_tracker/shared/_filter_pill_stream.html.erb +1 -1
- data/app/views/llm_cost_tracker/tags/index.html.erb +3 -3
- data/app/views/llm_cost_tracker/tags/show.html.erb +10 -10
- data/config/routes.rb +2 -3
- data/lib/llm_cost_tracker/budget.rb +24 -26
- data/lib/llm_cost_tracker/capture/sdk_payload.rb +34 -0
- data/lib/llm_cost_tracker/capture/sse.rb +1 -0
- data/lib/llm_cost_tracker/capture/stream_collector.rb +32 -39
- data/lib/llm_cost_tracker/capture/stream_tracker.rb +17 -28
- data/lib/llm_cost_tracker/capture_verifier.rb +59 -0
- data/lib/llm_cost_tracker/charges/cost.rb +27 -0
- data/lib/llm_cost_tracker/{billing → charges}/cost_status.rb +14 -4
- data/lib/llm_cost_tracker/{billing → charges}/line_item.rb +41 -44
- data/lib/llm_cost_tracker/check.rb +5 -0
- data/lib/llm_cost_tracker/configuration.rb +13 -44
- data/lib/llm_cost_tracker/currency.rb +5 -0
- data/lib/llm_cost_tracker/doctor/ingestion_check.rb +15 -49
- data/lib/llm_cost_tracker/doctor/price_check.rb +1 -1
- data/lib/llm_cost_tracker/doctor/probe.rb +3 -4
- data/lib/llm_cost_tracker/doctor/schema_check.rb +3 -6
- data/lib/llm_cost_tracker/doctor.rb +5 -69
- data/lib/llm_cost_tracker/engine.rb +4 -4
- data/lib/llm_cost_tracker/event.rb +12 -18
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +2 -3
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +5 -2
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_calls.rb.erb +4 -5
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +4 -3
- data/lib/llm_cost_tracker/ingestion/batch.rb +61 -12
- data/lib/llm_cost_tracker/ingestion/inbox.rb +7 -8
- data/lib/llm_cost_tracker/ingestion/pool.rb +3 -11
- data/lib/llm_cost_tracker/ingestion/worker.rb +7 -17
- data/lib/llm_cost_tracker/ingestion.rb +24 -36
- data/lib/llm_cost_tracker/integrations/anthropic.rb +93 -106
- data/lib/llm_cost_tracker/integrations/base.rb +39 -57
- data/lib/llm_cost_tracker/integrations/openai/batch_capture.rb +84 -0
- data/lib/llm_cost_tracker/integrations/openai/patches.rb +81 -0
- data/lib/llm_cost_tracker/integrations/openai.rb +70 -276
- data/lib/llm_cost_tracker/integrations/ruby_llm.rb +87 -99
- data/lib/llm_cost_tracker/integrations.rb +32 -25
- data/lib/llm_cost_tracker/ledger/period/totals.rb +27 -42
- data/lib/llm_cost_tracker/ledger/period.rb +5 -10
- data/lib/llm_cost_tracker/ledger/rollups.rb +67 -98
- data/lib/llm_cost_tracker/ledger/schema/adapter.rb +12 -13
- data/lib/llm_cost_tracker/ledger/schema/base.rb +51 -0
- data/lib/llm_cost_tracker/ledger/schema/call_line_items.rb +24 -79
- data/lib/llm_cost_tracker/ledger/schema/call_rollups.rb +3 -35
- data/lib/llm_cost_tracker/ledger/schema/call_tags.rb +4 -41
- data/lib/llm_cost_tracker/ledger/schema/calls.rb +30 -99
- data/lib/llm_cost_tracker/ledger/schema/ingestion/inbox_entries.rb +26 -0
- data/lib/llm_cost_tracker/ledger/schema/ingestion/leases.rb +17 -0
- data/lib/llm_cost_tracker/ledger/schema.rb +26 -0
- data/lib/llm_cost_tracker/ledger/store.rb +24 -41
- data/lib/llm_cost_tracker/ledger/tags/{sql.rb → breakdown.rb} +1 -1
- data/lib/llm_cost_tracker/ledger/tags/encoding.rb +4 -6
- data/lib/llm_cost_tracker/ledger.rb +8 -18
- data/lib/llm_cost_tracker/logging.rb +4 -21
- data/lib/llm_cost_tracker/middleware/faraday.rb +61 -50
- data/lib/llm_cost_tracker/parsers.rb +139 -26
- data/lib/llm_cost_tracker/prices.json +1821 -13
- data/lib/llm_cost_tracker/pricing/backfill.rb +52 -80
- data/lib/llm_cost_tracker/pricing/calculation.rb +279 -0
- data/lib/llm_cost_tracker/pricing/effective_prices.rb +17 -18
- data/lib/llm_cost_tracker/pricing/estimator.rb +2 -2
- data/lib/llm_cost_tracker/pricing/matcher.rb +98 -0
- data/lib/llm_cost_tracker/pricing/mode.rb +44 -56
- data/lib/llm_cost_tracker/pricing/price_key.rb +56 -0
- data/lib/llm_cost_tracker/pricing/rate.rb +18 -0
- data/lib/llm_cost_tracker/pricing/registry.rb +189 -100
- data/lib/llm_cost_tracker/pricing/service_rates.rb +69 -0
- data/lib/llm_cost_tracker/pricing/source.rb +7 -0
- data/lib/llm_cost_tracker/pricing/sync/fetcher.rb +2 -3
- data/lib/llm_cost_tracker/pricing/sync/registry_diff.rb +4 -10
- data/lib/llm_cost_tracker/pricing/sync/registry_writer.rb +10 -3
- data/lib/llm_cost_tracker/pricing/sync.rb +9 -11
- data/lib/llm_cost_tracker/pricing/unknown.rb +1 -5
- data/lib/llm_cost_tracker/pricing.rb +10 -278
- data/lib/llm_cost_tracker/providers/anthropic/parser.rb +93 -0
- data/lib/llm_cost_tracker/providers/anthropic/response_parser.rb +30 -0
- data/lib/llm_cost_tracker/providers/anthropic/usage_extractor.rb +76 -0
- data/lib/llm_cost_tracker/providers/azure/hosts.rb +1 -4
- data/lib/llm_cost_tracker/providers/azure/parser.rb +44 -0
- data/lib/llm_cost_tracker/providers/gemini/model_families.rb +1 -4
- data/lib/llm_cost_tracker/providers/gemini/parser.rb +177 -0
- data/lib/llm_cost_tracker/providers/gemini/usage_extractor.rb +76 -0
- data/lib/llm_cost_tracker/providers/openai/hosts.rb +1 -7
- data/lib/llm_cost_tracker/providers/openai/model_families.rb +5 -8
- data/lib/llm_cost_tracker/providers/openai/parser.rb +39 -0
- data/lib/llm_cost_tracker/providers/openai/response_parser.rb +152 -0
- data/lib/llm_cost_tracker/providers/openai/service_charges.rb +63 -39
- data/lib/llm_cost_tracker/providers/openai/usage_extractor.rb +72 -0
- data/lib/llm_cost_tracker/providers/openai_compatible/parser.rb +36 -0
- data/lib/llm_cost_tracker/providers.rb +35 -0
- data/lib/llm_cost_tracker/railtie.rb +0 -3
- data/lib/llm_cost_tracker/report/data.rb +3 -4
- data/lib/llm_cost_tracker/report/formatter.rb +1 -1
- data/lib/llm_cost_tracker/report.rb +1 -1
- data/lib/llm_cost_tracker/retention.rb +6 -19
- data/lib/llm_cost_tracker/tags/context.rb +9 -6
- data/lib/llm_cost_tracker/tags/sanitizer.rb +10 -0
- data/lib/llm_cost_tracker/timing.rb +2 -4
- data/lib/llm_cost_tracker/tracker.rb +23 -36
- data/lib/llm_cost_tracker/usage/catalog.rb +69 -0
- data/lib/llm_cost_tracker/usage/dimension.rb +21 -0
- data/lib/llm_cost_tracker/{billing/components.yml → usage/dimensions.yml} +6 -52
- data/lib/llm_cost_tracker/usage/source.rb +14 -0
- data/lib/llm_cost_tracker/usage/token_usage.rb +45 -0
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +43 -52
- data/lib/tasks/llm_cost_tracker.rake +14 -73
- metadata +81 -55
- data/app/controllers/llm_cost_tracker/reconciliation_controller.rb +0 -100
- data/app/helpers/llm_cost_tracker/dashboard_filter_helper.rb +0 -28
- data/app/helpers/llm_cost_tracker/reconciliation_helper.rb +0 -13
- data/app/models/llm_cost_tracker/provider_invoice.rb +0 -13
- data/app/models/llm_cost_tracker/provider_invoice_import.rb +0 -29
- data/app/views/llm_cost_tracker/reconciliation/index.html.erb +0 -174
- data/lib/llm_cost_tracker/billing/components.rb +0 -95
- data/lib/llm_cost_tracker/capture/stream.rb +0 -9
- data/lib/llm_cost_tracker/doctor/capture_verifier.rb +0 -61
- data/lib/llm_cost_tracker/doctor/check.rb +0 -7
- data/lib/llm_cost_tracker/doctor/cost_drift_check.rb +0 -56
- data/lib/llm_cost_tracker/doctor/invoice_reconciliation_check.rb +0 -164
- data/lib/llm_cost_tracker/doctor/legacy_audit_check.rb +0 -34
- data/lib/llm_cost_tracker/doctor/legacy_billing_status_check.rb +0 -20
- data/lib/llm_cost_tracker/doctor/pricing_snapshot_drift_check.rb +0 -85
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/reconciliation_generator.rb +0 -34
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_reconciliation.rb.erb +0 -60
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoice_imports_provider.rb.erb +0 -36
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_provider_invoices_metadata_index.rb.erb +0 -27
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoice_imports_provider_generator.rb +0 -31
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_provider_invoices_metadata_index_generator.rb +0 -31
- data/lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb +0 -40
- data/lib/llm_cost_tracker/ledger/schema/ingestion_inbox_entries.rb +0 -57
- data/lib/llm_cost_tracker/ledger/schema/ingestion_leases.rb +0 -52
- data/lib/llm_cost_tracker/ledger/schema/provider_invoice_imports.rb +0 -56
- data/lib/llm_cost_tracker/ledger/schema/provider_invoices.rb +0 -72
- data/lib/llm_cost_tracker/masking.rb +0 -39
- data/lib/llm_cost_tracker/parsers/anthropic.rb +0 -176
- data/lib/llm_cost_tracker/parsers/azure.rb +0 -46
- data/lib/llm_cost_tracker/parsers/base.rb +0 -131
- data/lib/llm_cost_tracker/parsers/gemini.rb +0 -230
- data/lib/llm_cost_tracker/parsers/openai.rb +0 -41
- data/lib/llm_cost_tracker/parsers/openai_compatible.rb +0 -45
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +0 -228
- data/lib/llm_cost_tracker/pricing/explainer.rb +0 -74
- data/lib/llm_cost_tracker/pricing/lookup.rb +0 -236
- data/lib/llm_cost_tracker/pricing/service_charges.rb +0 -206
- data/lib/llm_cost_tracker/providers/anthropic/server_tools.rb +0 -15
- data/lib/llm_cost_tracker/providers/anthropic/tier_classification.rb +0 -22
- data/lib/llm_cost_tracker/reconcile_tasks.rb +0 -131
- data/lib/llm_cost_tracker/reconciliation/diff.rb +0 -409
- data/lib/llm_cost_tracker/reconciliation/diff_result.rb +0 -44
- data/lib/llm_cost_tracker/reconciliation/import_result.rb +0 -19
- data/lib/llm_cost_tracker/reconciliation/importer.rb +0 -249
- data/lib/llm_cost_tracker/reconciliation/sources/anthropic_usage.rb +0 -148
- data/lib/llm_cost_tracker/reconciliation/sources/coercion.rb +0 -40
- data/lib/llm_cost_tracker/reconciliation/sources/fingerprint.rb +0 -20
- data/lib/llm_cost_tracker/reconciliation/sources/openai_usage.rb +0 -118
- data/lib/llm_cost_tracker/reconciliation.rb +0 -118
- data/lib/llm_cost_tracker/token_usage.rb +0 -93
|
@@ -35,7 +35,7 @@ module LlmCostTracker
|
|
|
35
35
|
calls: calls,
|
|
36
36
|
total_cost: row.total_cost,
|
|
37
37
|
average_cost_per_call: row.average_cost_per_call,
|
|
38
|
-
share_percent:
|
|
38
|
+
share_percent: total.positive? ? (calls.to_f / total) * 100.0 : 0.0
|
|
39
39
|
)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -115,12 +115,6 @@ module LlmCostTracker
|
|
|
115
115
|
def quoted_key
|
|
116
116
|
scope.connection.quote(key)
|
|
117
117
|
end
|
|
118
|
-
|
|
119
|
-
def percentage(numerator, denominator)
|
|
120
|
-
return 0.0 unless denominator.positive?
|
|
121
|
-
|
|
122
|
-
(numerator / denominator.to_f) * 100.0
|
|
123
|
-
end
|
|
124
118
|
end
|
|
125
119
|
end
|
|
126
120
|
end
|
|
@@ -20,7 +20,7 @@ module LlmCostTracker
|
|
|
20
20
|
|
|
21
21
|
def rows
|
|
22
22
|
scope.klass.find_by_sql(build_sql)
|
|
23
|
-
rescue
|
|
23
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
24
24
|
LlmCostTracker::Logging.warn("Tag key discovery failed (#{connection.adapter_name}): #{e.class}: #{e.message}")
|
|
25
25
|
[]
|
|
26
26
|
end
|
|
@@ -79,12 +79,6 @@
|
|
|
79
79
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
|
|
80
80
|
Pricing
|
|
81
81
|
<% end %>
|
|
82
|
-
<% if LlmCostTracker.reconciliation_enabled? %>
|
|
83
|
-
<%= link_to reconciliation_path, class: ("lct-sidebar-link lct-active" if dashboard_section == :reconciliation) || "lct-sidebar-link", aria: (dashboard_section == :reconciliation ? { current: "page" } : {}) do %>
|
|
84
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
|
85
|
-
Reconciliation
|
|
86
|
-
<% end %>
|
|
87
|
-
<% end %>
|
|
88
82
|
</div>
|
|
89
83
|
</aside>
|
|
90
84
|
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<%= link_to "× Clear filters", calls_path(current_query(provider: nil, model: nil, stream: nil, cost_status: nil, page: nil)), class: "lct-filter-clear" %>
|
|
19
19
|
<% end %>
|
|
20
20
|
|
|
21
|
-
<span class="lct-filter-row-meta"><%=
|
|
22
|
-
<%= link_to "Export CSV", calls_path(current_query(format: :csv)), class: "lct-page-link", title: "Capped at #{
|
|
21
|
+
<span class="lct-filter-row-meta"><%= number_with_delimiter(@calls_count) %> call<%= "s" unless @calls_count == 1 %> · <%= money(@calls_total_cost) %></span>
|
|
22
|
+
<%= link_to "Export CSV", calls_path(current_query(format: :csv)), class: "lct-page-link", title: "Capped at #{number_with_delimiter(LlmCostTracker::CallsController::CSV_EXPORT_LIMIT)} rows per request" %>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<% if @calls_count.zero? %>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<table class="lct-tbl">
|
|
33
33
|
<thead>
|
|
34
34
|
<tr>
|
|
35
|
-
<%= sortable_header("Tracked at", "tracked_at", num: true) %>
|
|
35
|
+
<%= sortable_header("Tracked at", "tracked_at", num: true, default: true) %>
|
|
36
36
|
<%= sortable_header("Provider", "provider") %>
|
|
37
37
|
<%= sortable_header("Model", "model") %>
|
|
38
38
|
<%= sortable_header("Input", "input", num: true) %>
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
<td><%= format_date(call.tracked_at) %></td>
|
|
50
50
|
<td><span class="lct-model-cell"><span class="lct-provider-dot lct-provider-dot-<%= call.provider %>"></span><%= call.provider %></span></td>
|
|
51
51
|
<td><code class="lct-code-id"><%= call.model %></code></td>
|
|
52
|
-
<td class="lct-num"><%=
|
|
53
|
-
<td class="lct-num"><%=
|
|
52
|
+
<td class="lct-num"><%= number_with_delimiter(call.input_tokens) %></td>
|
|
53
|
+
<td class="lct-num"><%= number_with_delimiter(call.output_tokens) %></td>
|
|
54
54
|
<td class="lct-num<%= ' lct-num-muted' if call.total_cost.nil? %>"><%= optional_money(call.total_cost) %></td>
|
|
55
|
-
<td class="lct-num<%= ' lct-num-muted' if call.latency_ms.nil? %>"><%= call.latency_ms ? "#{
|
|
56
|
-
<td><%= render "llm_cost_tracker/shared/tag_chips", tags: call.
|
|
55
|
+
<td class="lct-num<%= ' lct-num-muted' if call.latency_ms.nil? %>"><%= call.latency_ms ? "#{number_with_delimiter(call.latency_ms)}ms" : "n/a" %></td>
|
|
56
|
+
<td><%= render "llm_cost_tracker/shared/tag_chips", tags: call.tag_pairs %></td>
|
|
57
57
|
<td class="lct-num"><%= link_to "Details", call_path(call), class: "lct-page-link" %></td>
|
|
58
58
|
</tr>
|
|
59
59
|
<% end %>
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
<% last_row = [@page.offset + @calls.length, @calls_count].min %>
|
|
65
65
|
<% total_pages = @page.total_pages(@calls_count) %>
|
|
66
66
|
<div class="lct-pagination">
|
|
67
|
-
<span class="lct-pagination-info">Showing <strong><%=
|
|
67
|
+
<span class="lct-pagination-info">Showing <strong><%= number_with_delimiter(first_row) %></strong>–<strong><%= number_with_delimiter(last_row) %></strong> of <strong><%= number_with_delimiter(@calls_count) %></strong></span>
|
|
68
68
|
<span class="lct-seg lct-pagination-perpage" title="Rows per page">
|
|
69
69
|
<% LlmCostTracker::PaginationHelper::PER_PAGE_CHOICES.each do |choice| %>
|
|
70
70
|
<% if choice == @page.per %>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<% token_segments = priced_components.map do |component|
|
|
4
4
|
token_key = component.fetch(:token_key)
|
|
5
5
|
value = @call.has_attribute?(token_key) ? @call[token_key] : 0
|
|
6
|
-
{ label: component.fetch(:label), value: value, formatted_value:
|
|
6
|
+
{ label: component.fetch(:label), value: value, formatted_value: number_with_delimiter(value), css_class: component.fetch(:css_class) }
|
|
7
7
|
end %>
|
|
8
8
|
<% cost_segments = [] %>
|
|
9
9
|
<% unless @call.total_cost.nil? %>
|
|
@@ -33,11 +33,11 @@ end %>
|
|
|
33
33
|
</div>
|
|
34
34
|
<div class="lct-stat">
|
|
35
35
|
<div class="lct-stat-head"><p class="lct-stat-label">Total tokens</p></div>
|
|
36
|
-
<p class="lct-stat-value"><%=
|
|
36
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@call.total_tokens) %></p>
|
|
37
37
|
</div>
|
|
38
38
|
<div class="lct-stat">
|
|
39
39
|
<div class="lct-stat-head"><p class="lct-stat-label">Latency</p></div>
|
|
40
|
-
<p class="lct-stat-value<%= ' lct-num-muted' if @call.latency_ms.nil? %>"><%= @call.latency_ms ?
|
|
40
|
+
<p class="lct-stat-value<%= ' lct-num-muted' if @call.latency_ms.nil? %>"><%= @call.latency_ms ? number_with_delimiter(@call.latency_ms) : "n/a" %><% if @call.latency_ms %><span class="lct-stat-unit">ms</span><% end %></p>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
43
43
|
|
|
@@ -80,33 +80,41 @@ end %>
|
|
|
80
80
|
<dd><code class="lct-code-id"><%= @call.usage_source %></code></dd>
|
|
81
81
|
</div>
|
|
82
82
|
<% end %>
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
83
|
+
<% if @call.provider_response_id.present? %>
|
|
84
|
+
<div class="lct-meta-strip-item">
|
|
85
|
+
<dt>Response ID</dt>
|
|
86
|
+
<dd><%= content_tag(:code, @call.provider_response_id, class: "lct-code-id") %></dd>
|
|
87
|
+
</div>
|
|
88
|
+
<% end %>
|
|
89
|
+
<% if @call.provider_project_id.present? %>
|
|
90
|
+
<div class="lct-meta-strip-item">
|
|
91
|
+
<dt>Project ID</dt>
|
|
92
|
+
<dd><%= content_tag(:code, LlmCostTracker::Dashboard::Masking.mask_value(:provider_project_id, @call.provider_project_id), class: "lct-code-id") %></dd>
|
|
93
|
+
</div>
|
|
94
|
+
<% end %>
|
|
95
|
+
<% if @call.provider_api_key_id.present? %>
|
|
96
|
+
<div class="lct-meta-strip-item">
|
|
97
|
+
<dt>API Key ID</dt>
|
|
98
|
+
<dd><%= content_tag(:code, LlmCostTracker::Dashboard::Masking.mask_value(:provider_api_key_id, @call.provider_api_key_id), class: "lct-code-id") %></dd>
|
|
99
|
+
</div>
|
|
100
|
+
<% end %>
|
|
101
|
+
<% if @call.provider_workspace_id.present? %>
|
|
102
|
+
<div class="lct-meta-strip-item">
|
|
103
|
+
<dt>Workspace ID</dt>
|
|
104
|
+
<dd><%= content_tag(:code, LlmCostTracker::Dashboard::Masking.mask_value(:provider_workspace_id, @call.provider_workspace_id), class: "lct-code-id") %></dd>
|
|
105
|
+
</div>
|
|
106
|
+
<% end %>
|
|
99
107
|
</dl>
|
|
100
108
|
</section>
|
|
101
109
|
|
|
102
110
|
<section class="lct-panel">
|
|
103
111
|
<div class="lct-panel-head"><h2 class="lct-panel-title">Tags</h2></div>
|
|
104
112
|
<div class="lct-panel-body">
|
|
105
|
-
<% if @call.
|
|
113
|
+
<% if @call.tag_pairs.empty? %>
|
|
106
114
|
<p class="lct-stack-empty">(untagged)</p>
|
|
107
115
|
<% else %>
|
|
108
116
|
<span class="lct-tag-chips">
|
|
109
|
-
<% @call.
|
|
117
|
+
<% @call.tag_pairs.each do |k, v| %>
|
|
110
118
|
<span class="lct-tag-chip"><span class="lct-tag-chip-key"><%= k %></span>=<%= v %></span>
|
|
111
119
|
<% end %>
|
|
112
120
|
</span>
|
|
@@ -134,9 +142,9 @@ end %>
|
|
|
134
142
|
<td><code class="lct-code-id"><%= line_item.kind %></code></td>
|
|
135
143
|
<td><%= line_item.unit %></td>
|
|
136
144
|
<td class="lct-num"><%= line_item.quantity %></td>
|
|
137
|
-
<td class="lct-num"><%= line_item.rate_amount ? "#{optional_money(line_item.rate_amount)} / #{line_item.rate_quantity}" : "n/a" %></td>
|
|
138
|
-
<% unknown_cost = line_item.cost.nil? || line_item.cost_status.to_s == LlmCostTracker::
|
|
139
|
-
<td class="lct-num<%= ' lct-num-muted' if unknown_cost %>"><%= unknown_cost ? "n/a" : money(line_item.cost) %></td>
|
|
145
|
+
<td class="lct-num"><%= line_item.rate_amount ? "#{optional_money(line_item.rate_amount, currency: line_item.currency)} / #{line_item.rate_quantity}" : "n/a" %></td>
|
|
146
|
+
<% unknown_cost = line_item.cost.nil? || line_item.cost_status.to_s == LlmCostTracker::Charges::CostStatus::UNKNOWN %>
|
|
147
|
+
<td class="lct-num<%= ' lct-num-muted' if unknown_cost %>"><%= unknown_cost ? "n/a" : money(line_item.cost, currency: line_item.currency) %></td>
|
|
140
148
|
<td><%= line_item.cost_status %></td>
|
|
141
149
|
</tr>
|
|
142
150
|
<% end %>
|
|
@@ -161,6 +169,6 @@ end %>
|
|
|
161
169
|
<h2 class="lct-panel-title">Metadata</h2>
|
|
162
170
|
<span class="lct-disclose-hint">show JSON</span>
|
|
163
171
|
</summary>
|
|
164
|
-
<pre class="lct-pre"><%= safe_json(LlmCostTracker::Masking.mask_hash(masked_metadata_hash(@call.read_attribute("metadata")))) %></pre>
|
|
172
|
+
<pre class="lct-pre"><%= safe_json(LlmCostTracker::Dashboard::Masking.mask_hash(masked_metadata_hash(@call.read_attribute("metadata")))) %></pre>
|
|
165
173
|
</details>
|
|
166
174
|
<% end %>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<% end %>
|
|
9
9
|
|
|
10
10
|
<span class="lct-filter-row-meta">
|
|
11
|
-
<%=
|
|
11
|
+
<%= number_with_delimiter(@stats.total_calls) %> call<%= "s" unless @stats.total_calls.to_i == 1 %> · <%= money(@stats.total_cost) %>
|
|
12
12
|
</span>
|
|
13
13
|
</div>
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<div class="lct-alert lct-alert-warn">
|
|
23
23
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
|
|
24
24
|
<span>
|
|
25
|
-
<strong><%=
|
|
25
|
+
<strong><%= number_with_delimiter(@stats.unknown_pricing_count) %> call<%= "s" unless @stats.unknown_pricing_count.to_i == 1 %> with incomplete pricing</strong>
|
|
26
26
|
· <%= percent(coverage_percent(@stats.unknown_pricing_count, @stats.total_calls)) %> of slice
|
|
27
27
|
— update <code>pricing_overrides</code> or <code>prices_file</code>.
|
|
28
28
|
</span>
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
<% badge = delta_badge(@stats.calls_delta_percent, mode: :neutral) %>
|
|
65
65
|
<span class="<%= badge[:css_class] %>"><%= badge[:text] %></span>
|
|
66
66
|
</div>
|
|
67
|
-
<p class="lct-stat-value"><%=
|
|
67
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@stats.total_calls) %></p>
|
|
68
68
|
</div>
|
|
69
69
|
<% if @stats.average_latency_ms %>
|
|
70
70
|
<div class="lct-stat lct-stat-ok">
|
|
71
71
|
<div class="lct-stat-head"><p class="lct-stat-label">Avg latency</p></div>
|
|
72
|
-
<p class="lct-stat-value"><%=
|
|
72
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@stats.average_latency_ms.round) %><span class="lct-stat-unit">ms</span></p>
|
|
73
73
|
</div>
|
|
74
74
|
<% else %>
|
|
75
75
|
<div class="lct-stat">
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
<% end %>
|
|
80
80
|
<div class="lct-stat <%= @stats.unknown_pricing_count.to_i.positive? ? 'lct-stat-warn' : 'lct-stat-ok' %>">
|
|
81
81
|
<div class="lct-stat-head"><p class="lct-stat-label">Incomplete pricing</p></div>
|
|
82
|
-
<p class="lct-stat-value"><%=
|
|
82
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@stats.unknown_pricing_count) %></p>
|
|
83
83
|
<p class="lct-stat-foot"><%= percent(coverage_percent(@stats.unknown_pricing_count, @stats.total_calls)) %> of slice</p>
|
|
84
84
|
</div>
|
|
85
85
|
</div>
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
<h2 class="lct-panel-title">Daily spend</h2>
|
|
116
116
|
<span class="lct-panel-meta">
|
|
117
117
|
<span class="lct-legend"><span class="lct-legend-dot lct-legend-dot-current"></span>current</span>
|
|
118
|
-
<% if @comparison_series.any? %><span class="lct-legend"><span class="lct-legend-dot lct-legend-dot-prior"></span>prior <%=
|
|
118
|
+
<% if @comparison_series.any? %><span class="lct-legend"><span class="lct-legend-dot lct-legend-dot-prior"></span>prior <%= number_with_delimiter(@comparison_series.size) %>d</span><% end %>
|
|
119
119
|
</span>
|
|
120
120
|
</div>
|
|
121
121
|
<%= render "llm_cost_tracker/shared/spend_chart", series: @time_series, comparison_series: @comparison_series %>
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
<% @top_models.first(5).each do |row| %>
|
|
134
134
|
<tr>
|
|
135
135
|
<td><span class="lct-model-cell"><span class="lct-provider-dot lct-provider-dot-<%= row.provider %>"></span><%= link_to calls_path(calls_query_for_model(provider: row.provider, model: row.model)), class: "lct-code-id" do %><%= row.model %><% end %></span></td>
|
|
136
|
-
<td class="lct-num"><%=
|
|
136
|
+
<td class="lct-num"><%= number_with_delimiter(row.calls) %></td>
|
|
137
137
|
<td class="lct-num"><%= money(row.total_cost) %></td>
|
|
138
138
|
</tr>
|
|
139
139
|
<% end %>
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
<% @providers.each do |row| %>
|
|
151
151
|
<tr>
|
|
152
152
|
<td><span class="lct-model-cell"><span class="lct-provider-dot lct-provider-dot-<%= row.provider %>"></span><%= row.provider %></span></td>
|
|
153
|
-
<td class="lct-num"><%=
|
|
153
|
+
<td class="lct-num"><%= number_with_delimiter(row.calls) %></td>
|
|
154
154
|
<td class="lct-num"><%= money(row.total_cost) %></td>
|
|
155
155
|
<td class="lct-num"><%= percent(row.share_percent) %></td>
|
|
156
156
|
</tr>
|
|
@@ -7,9 +7,20 @@
|
|
|
7
7
|
<%= link_to "× Clear filters", data_quality_path(current_query(provider: nil, model: nil, page: nil)), class: "lct-filter-clear" %>
|
|
8
8
|
<% end %>
|
|
9
9
|
|
|
10
|
-
<span class="lct-filter-row-meta"><%=
|
|
10
|
+
<span class="lct-filter-row-meta"><%= number_with_delimiter(@summary.total) %> call<%= "s" unless @summary.total == 1 %> inspected</span>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
+
<% if @quarantined_inbox && @quarantined_inbox.count.positive? %>
|
|
14
|
+
<h3 class="lct-stat-section-label">Async inbox</h3>
|
|
15
|
+
<div class="lct-stat-grid">
|
|
16
|
+
<div class="lct-stat lct-stat-warn">
|
|
17
|
+
<div class="lct-stat-head"><p class="lct-stat-label">Quarantined inbox rows</p></div>
|
|
18
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@quarantined_inbox.count) %></p>
|
|
19
|
+
<p class="lct-stat-foot"><%= money(@quarantined_inbox.total_cost) %> excluded from the ledger and totals</p>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
13
24
|
<% if @summary.total.zero? %>
|
|
14
25
|
<section class="lct-panel lct-empty">
|
|
15
26
|
<h2 class="lct-state-title">No data yet</h2>
|
|
@@ -20,19 +31,19 @@
|
|
|
20
31
|
<div class="lct-stat-grid">
|
|
21
32
|
<div class="lct-stat">
|
|
22
33
|
<div class="lct-stat-head"><p class="lct-stat-label">Calls inspected</p></div>
|
|
23
|
-
<p class="lct-stat-value"><%=
|
|
34
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@summary.total) %></p>
|
|
24
35
|
<p class="lct-stat-foot">in current slice</p>
|
|
25
36
|
</div>
|
|
26
37
|
|
|
27
38
|
<div class="lct-stat">
|
|
28
39
|
<div class="lct-stat-head"><p class="lct-stat-label">Streaming calls</p></div>
|
|
29
|
-
<p class="lct-stat-value"><%=
|
|
40
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@summary.streaming_count) %></p>
|
|
30
41
|
<p class="lct-stat-foot"><%= percent(@summary.streaming_share) %> of calls</p>
|
|
31
42
|
</div>
|
|
32
43
|
|
|
33
44
|
<div class="lct-stat">
|
|
34
45
|
<div class="lct-stat-head"><p class="lct-stat-label">Calls with provider response ID</p></div>
|
|
35
|
-
<p class="lct-stat-value"><%=
|
|
46
|
+
<p class="lct-stat-value"><%= number_with_delimiter(@summary.calls_with_provider_response_id) %></p>
|
|
36
47
|
<p class="lct-stat-foot"><%= percent(@summary.provider_response_id_coverage) %> of calls</p>
|
|
37
48
|
</div>
|
|
38
49
|
</div>
|
|
@@ -50,7 +61,7 @@
|
|
|
50
61
|
<% if incomplete_pricing.positive? %>
|
|
51
62
|
<div class="lct-stat lct-stat-warn">
|
|
52
63
|
<div class="lct-stat-head"><p class="lct-stat-label">Incomplete pricing</p></div>
|
|
53
|
-
<p class="lct-stat-value"><%=
|
|
64
|
+
<p class="lct-stat-value"><%= number_with_delimiter(incomplete_pricing) %></p>
|
|
54
65
|
<p class="lct-stat-foot"><%= percent(@summary.unknown_pricing_share) %> of calls</p>
|
|
55
66
|
</div>
|
|
56
67
|
<% end %>
|
|
@@ -58,7 +69,7 @@
|
|
|
58
69
|
<% if untagged.positive? %>
|
|
59
70
|
<div class="lct-stat lct-stat-warn">
|
|
60
71
|
<div class="lct-stat-head"><p class="lct-stat-label">Calls without tags</p></div>
|
|
61
|
-
<p class="lct-stat-value"><%=
|
|
72
|
+
<p class="lct-stat-value"><%= number_with_delimiter(untagged) %></p>
|
|
62
73
|
<p class="lct-stat-foot"><%= percent(@summary.untagged_share) %> of calls</p>
|
|
63
74
|
</div>
|
|
64
75
|
<% end %>
|
|
@@ -66,7 +77,7 @@
|
|
|
66
77
|
<% if missing_latency.positive? %>
|
|
67
78
|
<div class="lct-stat lct-stat-warn">
|
|
68
79
|
<div class="lct-stat-head"><p class="lct-stat-label">Missing latency</p></div>
|
|
69
|
-
<p class="lct-stat-value"><%=
|
|
80
|
+
<p class="lct-stat-value"><%= number_with_delimiter(missing_latency) %></p>
|
|
70
81
|
<p class="lct-stat-foot"><%= percent(@summary.missing_latency_share) %> of calls</p>
|
|
71
82
|
</div>
|
|
72
83
|
<% end %>
|
|
@@ -74,7 +85,7 @@
|
|
|
74
85
|
<% if streams_missing_usage.positive? %>
|
|
75
86
|
<div class="lct-stat lct-stat-warn">
|
|
76
87
|
<div class="lct-stat-head"><p class="lct-stat-label">Streams without usage</p></div>
|
|
77
|
-
<p class="lct-stat-value"><%=
|
|
88
|
+
<p class="lct-stat-value"><%= number_with_delimiter(streams_missing_usage) %></p>
|
|
78
89
|
<p class="lct-stat-foot"><%= percent(@summary.streaming_missing_usage_share) %> of streams</p>
|
|
79
90
|
</div>
|
|
80
91
|
<% end %>
|
|
@@ -83,118 +94,64 @@
|
|
|
83
94
|
<div class="lct-stat lct-stat-warn">
|
|
84
95
|
<div class="lct-stat-head"><p class="lct-stat-label">Hidden output share</p></div>
|
|
85
96
|
<p class="lct-stat-value"><%= percent(hidden_output_share) %></p>
|
|
86
|
-
<p class="lct-stat-foot"><%=
|
|
97
|
+
<p class="lct-stat-foot"><%= number_with_delimiter(@hidden_output_summary.fetch(:hidden_output_tokens)) %> of <%= number_with_delimiter(@hidden_output_summary.fetch(:output_tokens)) %> output tokens</p>
|
|
87
98
|
</div>
|
|
88
99
|
<% end %>
|
|
89
100
|
</div>
|
|
90
|
-
<% else %>
|
|
91
|
-
<div class="lct-alert lct-alert-info">
|
|
92
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
93
|
-
<span>No data-quality issues in this slice.</span>
|
|
94
|
-
</div>
|
|
95
101
|
<% end %>
|
|
96
102
|
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
<div class="lct-panel-head"><h2 class="lct-panel-title">Next actions</h2></div>
|
|
100
|
-
<p class="lct-panel-intro">Use these fixes to improve the trustworthiness of downstream cost reports.</p>
|
|
101
|
-
|
|
102
|
-
<table class="lct-tbl">
|
|
103
|
-
<thead>
|
|
104
|
-
<tr>
|
|
105
|
-
<th>Issue</th>
|
|
106
|
-
<th>Why it matters</th>
|
|
107
|
-
<th>Suggested action</th>
|
|
108
|
-
</tr>
|
|
109
|
-
</thead>
|
|
110
|
-
<tbody>
|
|
111
|
-
<tr>
|
|
112
|
-
<td>Incomplete pricing</td>
|
|
113
|
-
<td>Some or all line items have no configured rate, so totals undercount.</td>
|
|
114
|
-
<td>Update <code class="lct-code-id">pricing_overrides</code> or <code class="lct-code-id">prices_file</code>.</td>
|
|
115
|
-
</tr>
|
|
116
|
-
<tr>
|
|
117
|
-
<td>Missing tags</td>
|
|
118
|
-
<td>Attribution by tenant, user, or feature becomes less useful.</td>
|
|
119
|
-
<td>Pass <code class="lct-code-id">tags:</code> from middleware using request context.</td>
|
|
120
|
-
</tr>
|
|
121
|
-
<tr>
|
|
122
|
-
<td>Missing latency</td>
|
|
123
|
-
<td>Slow requests become harder to isolate on the calls page.</td>
|
|
124
|
-
<td>Make sure latency capture is enabled on every tracked request.</td>
|
|
125
|
-
</tr>
|
|
126
|
-
<% if @summary.streaming_missing_usage.positive? %>
|
|
127
|
-
<tr>
|
|
128
|
-
<td>Streams without usage</td>
|
|
129
|
-
<td>Token totals undercount when streaming responses drop the final usage event.</td>
|
|
130
|
-
<td>Send OpenAI requests with <code class="lct-code-id">stream_options: { include_usage: true }</code>, or wrap custom clients with <code class="lct-code-id">LlmCostTracker.track_stream</code>.</td>
|
|
131
|
-
</tr>
|
|
132
|
-
<% end %>
|
|
133
|
-
<% if @summary.missing_provider_response_id_count.positive? %>
|
|
134
|
-
<tr>
|
|
135
|
-
<td>Missing provider response IDs</td>
|
|
136
|
-
<td>Proof of provider-issued responses is weaker when calls cannot be tied back to provider objects.</td>
|
|
137
|
-
<td>Upgrade to the latest parser coverage and pass <code class="lct-code-id">provider_response_id:</code> for custom clients when the provider exposes one.</td>
|
|
138
|
-
</tr>
|
|
139
|
-
<% end %>
|
|
140
|
-
</tbody>
|
|
141
|
-
</table>
|
|
142
|
-
</section>
|
|
103
|
+
<section class="lct-panel">
|
|
104
|
+
<div class="lct-panel-head"><h2 class="lct-panel-title">Coverage summary</h2></div>
|
|
143
105
|
|
|
144
|
-
<
|
|
145
|
-
<
|
|
146
|
-
|
|
106
|
+
<table class="lct-tbl">
|
|
107
|
+
<thead>
|
|
108
|
+
<tr>
|
|
109
|
+
<th>Dimension</th>
|
|
110
|
+
<th class="lct-num">Coverage</th>
|
|
111
|
+
<th class="lct-num">Calls with data</th>
|
|
112
|
+
<th>Visual</th>
|
|
113
|
+
</tr>
|
|
114
|
+
</thead>
|
|
115
|
+
<tbody>
|
|
116
|
+
<tr>
|
|
117
|
+
<td>Cost (pricing known)</td>
|
|
118
|
+
<td class="lct-num"><%= percent(@summary.cost_coverage) %></td>
|
|
119
|
+
<td class="lct-num"><%= number_with_delimiter(@summary.calls_with_pricing) %></td>
|
|
120
|
+
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.cost_coverage, max: 100.0 %></td>
|
|
121
|
+
</tr>
|
|
147
122
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<th>Visual</th>
|
|
155
|
-
</tr>
|
|
156
|
-
</thead>
|
|
157
|
-
<tbody>
|
|
158
|
-
<tr>
|
|
159
|
-
<td>Cost (pricing known)</td>
|
|
160
|
-
<td class="lct-num"><%= percent(@summary.cost_coverage) %></td>
|
|
161
|
-
<td class="lct-num"><%= number(@summary.calls_with_pricing) %></td>
|
|
162
|
-
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.cost_coverage, max: 100.0 %></td>
|
|
163
|
-
</tr>
|
|
123
|
+
<tr>
|
|
124
|
+
<td>Tags (at least one tag)</td>
|
|
125
|
+
<td class="lct-num"><%= percent(@summary.tag_coverage) %></td>
|
|
126
|
+
<td class="lct-num"><%= number_with_delimiter(@summary.tagged_calls) %></td>
|
|
127
|
+
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.tag_coverage, max: 100.0 %></td>
|
|
128
|
+
</tr>
|
|
164
129
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
130
|
+
<tr>
|
|
131
|
+
<td>Latency</td>
|
|
132
|
+
<td class="lct-num"><%= percent(@summary.latency_coverage) %></td>
|
|
133
|
+
<td class="lct-num"><%= number_with_delimiter(@summary.calls_with_latency) %></td>
|
|
134
|
+
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.latency_coverage, max: 100.0 %></td>
|
|
135
|
+
</tr>
|
|
171
136
|
|
|
137
|
+
<% if @summary.streaming_count.positive? %>
|
|
172
138
|
<tr>
|
|
173
|
-
<td>
|
|
174
|
-
<td class="lct-num"><%= percent(@summary.
|
|
175
|
-
<td class="lct-num"><%=
|
|
176
|
-
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.
|
|
139
|
+
<td>Streaming usage captured</td>
|
|
140
|
+
<td class="lct-num"><%= percent(@summary.stream_coverage) %></td>
|
|
141
|
+
<td class="lct-num"><%= number_with_delimiter(@summary.streams_with_usage) %> / <%= number_with_delimiter(@summary.streaming_count) %></td>
|
|
142
|
+
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.stream_coverage, max: 100.0 %></td>
|
|
177
143
|
</tr>
|
|
144
|
+
<% end %>
|
|
178
145
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
<tr>
|
|
189
|
-
<td>Provider response ID</td>
|
|
190
|
-
<td class="lct-num"><%= percent(@summary.provider_response_id_coverage) %></td>
|
|
191
|
-
<td class="lct-num"><%= number(@summary.calls_with_provider_response_id) %></td>
|
|
192
|
-
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.provider_response_id_coverage, max: 100.0 %></td>
|
|
193
|
-
</tr>
|
|
194
|
-
</tbody>
|
|
195
|
-
</table>
|
|
196
|
-
</section>
|
|
197
|
-
</div>
|
|
146
|
+
<tr>
|
|
147
|
+
<td>Provider response ID</td>
|
|
148
|
+
<td class="lct-num"><%= percent(@summary.provider_response_id_coverage) %></td>
|
|
149
|
+
<td class="lct-num"><%= number_with_delimiter(@summary.calls_with_provider_response_id) %></td>
|
|
150
|
+
<td><%= render "llm_cost_tracker/shared/bar", value: @summary.provider_response_id_coverage, max: 100.0 %></td>
|
|
151
|
+
</tr>
|
|
152
|
+
</tbody>
|
|
153
|
+
</table>
|
|
154
|
+
</section>
|
|
198
155
|
|
|
199
156
|
<section class="lct-panel">
|
|
200
157
|
<div class="lct-panel-head"><h2 class="lct-panel-title">Token usage</h2></div>
|
|
@@ -216,7 +173,7 @@
|
|
|
216
173
|
<% cost_value = row.fetch(:cost_value) %>
|
|
217
174
|
<tr>
|
|
218
175
|
<td><%= LlmCostTracker::TokenUsageHelper::QUALITY_LABELS.fetch(token_key) %></td>
|
|
219
|
-
<td class="lct-num"><%=
|
|
176
|
+
<td class="lct-num"><%= number_with_delimiter(row.fetch(:token_value)) %></td>
|
|
220
177
|
<% if row.fetch(:share_basis) == :output %>
|
|
221
178
|
<td class="lct-num"><%= percent(row.fetch(:share_percent)) %> of output</td>
|
|
222
179
|
<% else %>
|
|
@@ -250,13 +207,13 @@
|
|
|
250
207
|
</thead>
|
|
251
208
|
<tbody>
|
|
252
209
|
<% @service_charge_rows.each do |row| %>
|
|
253
|
-
<% unknown_cost = row.cost_status.to_s == LlmCostTracker::
|
|
210
|
+
<% unknown_cost = row.cost_status.to_s == LlmCostTracker::Charges::CostStatus::UNKNOWN %>
|
|
254
211
|
<tr>
|
|
255
212
|
<td><code class="lct-code-id"><%= row.provider %></code></td>
|
|
256
213
|
<td><code class="lct-code-id"><%= row.component %></code></td>
|
|
257
214
|
<td><%= row.cost_status %></td>
|
|
258
|
-
<td class="lct-num"><%=
|
|
259
|
-
<td class="lct-num"><%=
|
|
215
|
+
<td class="lct-num"><%= number_with_delimiter(row.charges_count) %></td>
|
|
216
|
+
<td class="lct-num"><%= number_with_delimiter(row.quantity) %></td>
|
|
260
217
|
<td class="lct-num<%= ' lct-num-muted' if unknown_cost %>"><%= unknown_cost ? "n/a" : money(row.total_cost) %></td>
|
|
261
218
|
</tr>
|
|
262
219
|
<% end %>
|
|
@@ -269,7 +226,6 @@
|
|
|
269
226
|
<% if @streaming_health_rows.any? %>
|
|
270
227
|
<section class="lct-panel">
|
|
271
228
|
<div class="lct-panel-head"><h2 class="lct-panel-title">Streaming health by provider</h2></div>
|
|
272
|
-
<p class="lct-panel-intro">Streams without a final usage chunk land as <code>usage_source: unknown</code> and undercount tokens. A high unknown share for an OpenAI-compatible provider usually means <code>stream_options: { include_usage: true }</code> is not being injected for that host.</p>
|
|
273
229
|
|
|
274
230
|
<div class="lct-table-wrap">
|
|
275
231
|
<table class="lct-tbl">
|
|
@@ -286,9 +242,9 @@
|
|
|
286
242
|
<% @streaming_health_rows.each do |row| %>
|
|
287
243
|
<tr>
|
|
288
244
|
<td><code class="lct-code-id"><%= row.provider %></code></td>
|
|
289
|
-
<td class="lct-num"><%=
|
|
290
|
-
<td class="lct-num"><%=
|
|
291
|
-
<td class="lct-num"><%=
|
|
245
|
+
<td class="lct-num"><%= number_with_delimiter(row.streams) %></td>
|
|
246
|
+
<td class="lct-num"><%= number_with_delimiter(row.with_usage) %></td>
|
|
247
|
+
<td class="lct-num"><%= number_with_delimiter(row.unknown) %></td>
|
|
292
248
|
<td class="lct-num"><%= percent(row.unknown_share) %></td>
|
|
293
249
|
</tr>
|
|
294
250
|
<% end %>
|
|
@@ -321,7 +277,7 @@
|
|
|
321
277
|
<tr>
|
|
322
278
|
<td><code class="lct-code-id"><%= row.provider %></code></td>
|
|
323
279
|
<td><code class="lct-code-id"><%= row.model %></code></td>
|
|
324
|
-
<td class="lct-num"><%=
|
|
280
|
+
<td class="lct-num"><%= number_with_delimiter(row.calls) %></td>
|
|
325
281
|
<td class="lct-num"><%= percent(row.share_percent) %></td>
|
|
326
282
|
</tr>
|
|
327
283
|
<% end %>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<%= link_to "× Clear filters", models_path(current_query(provider: nil, model: nil, page: nil)), class: "lct-filter-clear" %>
|
|
9
9
|
<% end %>
|
|
10
10
|
|
|
11
|
-
<span class="lct-filter-row-meta"><%=
|
|
11
|
+
<span class="lct-filter-row-meta"><%= number_with_delimiter(rows.size) %> model<%= "s" unless rows.size == 1 %></span>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
<% if rows.empty? %>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<%= sortable_header("Tokens", "tokens", num: true) %>
|
|
28
28
|
<%= sortable_header("Avg latency", "latency", num: true) %>
|
|
29
29
|
<%= sortable_header("Avg cost / call", "avg_cost", num: true) %>
|
|
30
|
-
<%= sortable_header("Total cost", "cost", num: true) %>
|
|
30
|
+
<%= sortable_header("Total cost", "cost", num: true, default: true) %>
|
|
31
31
|
<th></th>
|
|
32
32
|
</tr>
|
|
33
33
|
</thead>
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
<tr>
|
|
37
37
|
<td><span class="lct-model-cell"><span class="lct-provider-dot lct-provider-dot-<%= row.provider %>"></span><%= row.provider %></span></td>
|
|
38
38
|
<td><code class="lct-code-id"><%= row.model %></code></td>
|
|
39
|
-
<td class="lct-num"><%=
|
|
40
|
-
<td class="lct-num"><%=
|
|
39
|
+
<td class="lct-num"><%= number_with_delimiter(row.calls) %></td>
|
|
40
|
+
<td class="lct-num"><%= number_with_delimiter(row.total_tokens) %></td>
|
|
41
41
|
<% avg_latency = row.average_latency_ms %>
|
|
42
|
-
<td class="lct-num<%= ' lct-num-muted' if avg_latency.nil? %>"><%= avg_latency ? "#{
|
|
42
|
+
<td class="lct-num<%= ' lct-num-muted' if avg_latency.nil? %>"><%= avg_latency ? "#{number_with_delimiter(avg_latency.round)}ms" : "n/a" %></td>
|
|
43
43
|
<td class="lct-num"><%= money(row.average_cost_per_call) %></td>
|
|
44
44
|
<td class="lct-num"><%= money(row.total_cost) %></td>
|
|
45
45
|
<td class="lct-num"><%= link_to "Calls", calls_path(calls_query_for_model(provider: row.provider, model: row.model)), class: "lct-page-link" %></td>
|