Module: LlmCostTracker::ApplicationHelper

Includes:
ChartHelper, DashboardFilterOptionsHelper, DashboardQueryHelper, InlineStyleHelper, PaginationHelper, TokenUsageHelper
Defined in:
app/helpers/llm_cost_tracker/application_helper.rb

Constant Summary collapse

TAG_VALUE_SUMMARY_BYTES =
80
TAG_TOOLTIP_BYTES =
512

Constants included from InlineStyleHelper

InlineStyleHelper::UNSAFE_CSS_CHARS

Constants included from TokenUsageHelper

TokenUsageHelper::COMPONENT_LABELS, TokenUsageHelper::QUALITY_LABELS, TokenUsageHelper::STACK_CLASSES

Constants included from PaginationHelper

PaginationHelper::PER_PAGE_CHOICES

Constants included from DashboardFilterOptionsHelper

DashboardFilterOptionsHelper::MAX_FILTER_OPTIONS

Instance Method Summary collapse

Methods included from InlineStyleHelper

#inline_style, #inline_style_block

Methods included from TokenUsageHelper

#call_line_item_costs_by_component, #token_usage_stack_components

Methods included from PaginationHelper

#pagination_page_items

Methods included from ChartHelper

#spend_chart_svg

Methods included from DashboardQueryHelper

#calls_query_for_tag, #dashboard_filter_path

Methods included from DashboardFilterOptionsHelper

#filter_options_for

Instance Method Details

#bar_width(value, max) ⇒ Object



87
88
89
90
91
92
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 87

def bar_width(value, max)
  max = max.to_f
  return "0%" unless max.positive?

  "#{[(value.to_f / max) * 100.0, 100.0].min.round(2)}%"
end

#calls_query_for_model(provider:, model:) ⇒ Object



141
142
143
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 141

def calls_query_for_model(provider:, model:)
  current_query(provider: provider, model: model, page: nil, per: nil, format: nil)
end

#coverage_percent(numerator, denominator) ⇒ Object



28
29
30
31
32
33
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 28

def coverage_percent(numerator, denominator)
  denominator = denominator.to_f
  return 0.0 unless denominator.positive?

  (numerator.to_f / denominator) * 100.0
end

#current_query(overrides = {}) ⇒ Object



137
138
139
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 137

def current_query(overrides = {})
  request.query_parameters.symbolize_keys.merge(overrides)
end

#dashboard_sectionObject



17
18
19
20
21
22
23
24
25
26
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 17

def dashboard_section
  path = request.path.to_s
  return :models if path.start_with?(models_path)
  return :calls if path.start_with?(calls_path)
  return :tags if path.start_with?(tags_path)
  return :data_quality if path.start_with?(data_quality_path)
  return :pricing if path.start_with?(pricing_path)

  :overview
end

#delta_badge(delta_percent, mode: :cost) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 68

def delta_badge(delta_percent, mode: :cost)
  return { text: "n/a vs. prior", css_class: "lct-delta-badge lct-delta-neutral" } if delta_percent.nil?

  rounded = delta_percent.round(1)
  return { text: "0.0% vs. prior", css_class: "lct-delta-badge lct-delta-neutral" } if rounded.zero?

  sign = rounded.positive? ? "+" : ""
  text = "#{sign}#{format('%.1f', rounded)}% vs. prior"
  css_class = if mode == :neutral
                "lct-delta-badge lct-delta-neutral"
              elsif rounded.positive?
                "lct-delta-badge lct-delta-up"
              else
                "lct-delta-badge lct-delta-down"
              end

  { text: text, css_class: css_class }
end

#format_date(value) ⇒ Object



48
49
50
51
52
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 48

def format_date(value)
  return "" if value.nil?

  value.strftime("%Y-%m-%d %H:%M")
end

#masked_metadata_hash(value) ⇒ Object



113
114
115
116
117
118
119
120
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 113

def (value)
  return value if value.is_a?(Hash)
  return {} if value.nil?

  JSON.parse(value.to_s)
rescue JSON::ParserError, TypeError
  {}
end

#money(value, currency: LlmCostTracker::DEFAULT_CURRENCY) ⇒ Object



35
36
37
38
39
40
41
42
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 35

def money(value, currency: LlmCostTracker::DEFAULT_CURRENCY)
  value = value.to_f
  precision = value.abs < 0.01 && value != 0.0 ? 6 : 2
  formatted = format("%.#{precision}f", value)
  code = currency.to_s.upcase.presence || LlmCostTracker::DEFAULT_CURRENCY

  code == LlmCostTracker::DEFAULT_CURRENCY ? "$#{formatted}" : "#{formatted} #{code}"
end

#optional_money(value, currency: LlmCostTracker::DEFAULT_CURRENCY) ⇒ Object



44
45
46
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 44

def optional_money(value, currency: LlmCostTracker::DEFAULT_CURRENCY)
  value.nil? ? "n/a" : money(value, currency: currency)
end

#percent(value) ⇒ Object



64
65
66
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 64

def percent(value)
  "#{format('%.1f', value.to_f)}%"
end

#pricing_status(call) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 54

def pricing_status(call)
  return "Unknown" if call.total_cost.nil?

  {
    LlmCostTracker::Charges::CostStatus::COMPLETE => "Estimated",
    LlmCostTracker::Charges::CostStatus::FREE => "Free",
    LlmCostTracker::Charges::CostStatus::PARTIAL => "Partial"
  }.fetch(call.cost_status, "Unknown")
end

#safe_json(value) ⇒ Object



106
107
108
109
110
111
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 106

def safe_json(value)
  parsed = value.is_a?(String) ? JSON.parse(value) : value
  JSON.pretty_generate(parsed || {})
rescue JSON::ParserError, TypeError
  value.to_s
end

#stack_segments(entries) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 94

def stack_segments(entries)
  total = entries.sum { |entry| entry[:value].to_f }
  return [] unless total.positive?

  entries.filter_map do |entry|
    value = entry[:value].to_f
    next unless value.positive?

    entry.merge(percent: (value / total) * 100.0)
  end
end

#tag_chip_entries(tags, limit: 3) ⇒ Object



122
123
124
125
126
127
128
129
130
131
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 122

def tag_chip_entries(tags, limit: 3)
  normalized = normalized_tags(tags)
  return [] if normalized.empty?

  visible = normalized.first(limit).map do |key, value|
    { key: key.to_s, value: tag_value_summary(value) }
  end
  visible << { more: normalized.size - limit } if normalized.size > limit
  visible
end

#tag_chips_title(tags) ⇒ Object



133
134
135
# File 'app/helpers/llm_cost_tracker/application_helper.rb', line 133

def tag_chips_title(tags)
  truncate_text(safe_json(tags), TAG_TOOLTIP_BYTES)
end