llm_cost_tracker 0.7.0 → 0.7.2
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 +31 -0
- data/README.md +21 -16
- data/app/assets/llm_cost_tracker/application.css +3 -0
- data/app/controllers/llm_cost_tracker/application_controller.rb +22 -4
- data/app/controllers/llm_cost_tracker/calls_controller.rb +6 -11
- data/app/controllers/llm_cost_tracker/dashboard_controller.rb +2 -1
- data/app/controllers/llm_cost_tracker/data_quality_controller.rb +5 -1
- data/app/controllers/llm_cost_tracker/models_controller.rb +0 -1
- data/app/controllers/llm_cost_tracker/tags_controller.rb +1 -8
- data/app/helpers/llm_cost_tracker/application_helper.rb +2 -1
- data/app/helpers/llm_cost_tracker/dashboard_filter_helper.rb +1 -2
- data/app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb +1 -1
- data/app/helpers/llm_cost_tracker/dashboard_query_helper.rb +10 -27
- data/app/helpers/llm_cost_tracker/token_usage_helper.rb +58 -0
- data/app/models/llm_cost_tracker/ingestion/event.rb +13 -0
- data/app/models/llm_cost_tracker/ingestion/lease.rb +11 -0
- data/app/models/llm_cost_tracker/ledger/call.rb +45 -0
- data/app/models/llm_cost_tracker/ledger/call_metrics.rb +66 -0
- data/app/models/llm_cost_tracker/ledger/period/grouping.rb +71 -0
- data/app/models/llm_cost_tracker/ledger/period/total.rb +13 -0
- data/app/models/llm_cost_tracker/ledger/tags/accessors.rb +19 -0
- data/app/services/llm_cost_tracker/dashboard/data_quality.rb +111 -94
- data/app/services/llm_cost_tracker/dashboard/date_range.rb +2 -2
- data/app/services/llm_cost_tracker/dashboard/filter.rb +7 -18
- data/app/services/llm_cost_tracker/dashboard/overview_stats.rb +58 -67
- data/app/services/llm_cost_tracker/dashboard/pagination.rb +59 -0
- data/app/services/llm_cost_tracker/dashboard/params.rb +26 -0
- data/app/services/llm_cost_tracker/dashboard/provider_breakdown.rb +18 -20
- data/app/services/llm_cost_tracker/dashboard/spend_anomaly.rb +4 -13
- data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +28 -61
- data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +8 -21
- data/app/services/llm_cost_tracker/dashboard/time_series.rb +1 -1
- data/app/services/llm_cost_tracker/dashboard/top_models.rb +12 -47
- data/app/views/llm_cost_tracker/calls/index.html.erb +12 -18
- data/app/views/llm_cost_tracker/calls/show.html.erb +30 -32
- data/app/views/llm_cost_tracker/dashboard/index.html.erb +17 -19
- data/app/views/llm_cost_tracker/data_quality/index.html.erb +108 -135
- data/app/views/llm_cost_tracker/models/index.html.erb +8 -9
- data/app/views/llm_cost_tracker/shared/setup_required.html.erb +13 -2
- data/app/views/llm_cost_tracker/tags/show.html.erb +20 -20
- data/lib/llm_cost_tracker/budget.rb +8 -20
- data/lib/llm_cost_tracker/capture/stream.rb +9 -0
- data/lib/llm_cost_tracker/capture/stream_collector.rb +189 -0
- data/lib/llm_cost_tracker/{integrations → capture}/stream_tracker.rb +41 -73
- data/lib/llm_cost_tracker/configuration/instrumentation.rb +3 -7
- data/lib/llm_cost_tracker/configuration.rb +33 -36
- data/lib/llm_cost_tracker/doctor/capture_verifier.rb +61 -0
- data/lib/llm_cost_tracker/doctor/check.rb +7 -0
- data/lib/llm_cost_tracker/doctor/ingestion_check.rb +22 -59
- data/lib/llm_cost_tracker/doctor/price_check.rb +60 -0
- data/lib/llm_cost_tracker/doctor.rb +63 -71
- data/lib/llm_cost_tracker/errors.rb +4 -15
- data/lib/llm_cost_tracker/event.rb +6 -6
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_token_usage_generator.rb +42 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +2 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +7 -7
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +3 -3
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_token_usage_to_llm_api_calls.rb.erb +22 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +9 -14
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +0 -4
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb +12 -1
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb +2 -2
- data/lib/llm_cost_tracker/{storage/active_record_inbox_batch.rb → ingestion/batch.rb} +21 -20
- data/lib/llm_cost_tracker/ingestion/inbox.rb +105 -0
- data/lib/llm_cost_tracker/{storage/active_record_ingestor_lease.rb → ingestion/lease_claim.rb} +5 -7
- data/lib/llm_cost_tracker/{storage/active_record_ingestor.rb → ingestion/worker.rb} +38 -48
- data/lib/llm_cost_tracker/ingestion.rb +129 -0
- data/lib/llm_cost_tracker/integrations/anthropic.rb +66 -31
- data/lib/llm_cost_tracker/integrations/base.rb +73 -34
- data/lib/llm_cost_tracker/integrations/openai.rb +43 -37
- data/lib/llm_cost_tracker/integrations/ruby_llm.rb +40 -30
- data/lib/llm_cost_tracker/integrations.rb +43 -0
- data/lib/llm_cost_tracker/ledger/period/totals.rb +66 -0
- data/lib/llm_cost_tracker/{storage/active_record_periods.rb → ledger/period.rb} +2 -2
- data/lib/llm_cost_tracker/ledger/rollups/batch.rb +43 -0
- data/lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb +46 -0
- data/lib/llm_cost_tracker/ledger/rollups.rb +87 -0
- data/lib/llm_cost_tracker/ledger/schema/adapter.rb +51 -0
- data/lib/llm_cost_tracker/ledger/schema/calls.rb +101 -0
- data/lib/llm_cost_tracker/ledger/schema/period_totals.rb +32 -0
- data/lib/llm_cost_tracker/ledger/store.rb +60 -0
- data/lib/llm_cost_tracker/ledger/tags/query.rb +29 -0
- data/lib/llm_cost_tracker/ledger/tags/sql.rb +33 -0
- data/lib/llm_cost_tracker/ledger.rb +13 -0
- data/lib/llm_cost_tracker/logging.rb +3 -6
- data/lib/llm_cost_tracker/middleware/faraday.rb +88 -46
- data/lib/llm_cost_tracker/parsers/anthropic.rb +62 -29
- data/lib/llm_cost_tracker/parsers/base.rb +12 -21
- data/lib/llm_cost_tracker/parsers/gemini.rb +50 -25
- data/lib/llm_cost_tracker/parsers/openai.rb +27 -5
- data/lib/llm_cost_tracker/parsers/openai_compatible.rb +14 -4
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +58 -25
- data/lib/llm_cost_tracker/parsers/sse.rb +4 -7
- data/lib/llm_cost_tracker/parsers.rb +20 -0
- data/lib/llm_cost_tracker/prices.json +361 -36
- data/lib/llm_cost_tracker/pricing/components.rb +37 -0
- data/lib/llm_cost_tracker/pricing/effective_prices.rb +46 -50
- data/lib/llm_cost_tracker/pricing/explainer.rb +25 -30
- data/lib/llm_cost_tracker/pricing/lookup.rb +67 -46
- data/lib/llm_cost_tracker/pricing/registry.rb +156 -0
- data/lib/llm_cost_tracker/pricing/sync/fetcher.rb +107 -0
- data/lib/llm_cost_tracker/pricing/sync/registry_diff.rb +53 -0
- data/lib/llm_cost_tracker/pricing/sync/registry_loader.rb +63 -0
- data/lib/llm_cost_tracker/pricing/sync/registry_writer.rb +31 -0
- data/lib/llm_cost_tracker/pricing/sync.rb +159 -0
- data/lib/llm_cost_tracker/pricing/unknown.rb +46 -0
- data/lib/llm_cost_tracker/pricing.rb +33 -32
- data/lib/llm_cost_tracker/railtie.rb +7 -8
- data/lib/llm_cost_tracker/report/data.rb +72 -0
- data/lib/llm_cost_tracker/report/formatter.rb +69 -0
- data/lib/llm_cost_tracker/report.rb +8 -8
- data/lib/llm_cost_tracker/retention.rb +27 -10
- data/lib/llm_cost_tracker/tags/context.rb +35 -0
- data/lib/llm_cost_tracker/tags/key.rb +18 -0
- data/lib/llm_cost_tracker/tags/sanitizer.rb +68 -0
- data/lib/llm_cost_tracker/token_usage.rb +67 -0
- data/lib/llm_cost_tracker/tracker.rb +39 -69
- data/lib/llm_cost_tracker/usage_capture.rb +37 -0
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +56 -78
- data/lib/tasks/llm_cost_tracker.rake +18 -13
- metadata +54 -58
- data/app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb +0 -81
- data/app/services/llm_cost_tracker/pagination.rb +0 -57
- data/lib/llm_cost_tracker/active_record_adapter.rb +0 -53
- data/lib/llm_cost_tracker/capture_verifier.rb +0 -64
- data/lib/llm_cost_tracker/cost.rb +0 -12
- data/lib/llm_cost_tracker/doctor/capture_check.rb +0 -39
- data/lib/llm_cost_tracker/event_metadata.rb +0 -52
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_usage_breakdown_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_usage_breakdown_to_llm_api_calls.rb.erb +0 -29
- data/lib/llm_cost_tracker/inbox_event.rb +0 -9
- data/lib/llm_cost_tracker/ingestor_lease.rb +0 -9
- data/lib/llm_cost_tracker/integrations/object_reader.rb +0 -56
- data/lib/llm_cost_tracker/integrations/registry.rb +0 -71
- data/lib/llm_cost_tracker/llm_api_call.rb +0 -60
- data/lib/llm_cost_tracker/llm_api_call_metrics.rb +0 -63
- data/lib/llm_cost_tracker/parameter_hash.rb +0 -33
- data/lib/llm_cost_tracker/parsed_usage.rb +0 -72
- data/lib/llm_cost_tracker/parsers/registry.rb +0 -58
- data/lib/llm_cost_tracker/period_grouping.rb +0 -67
- data/lib/llm_cost_tracker/period_total.rb +0 -9
- data/lib/llm_cost_tracker/price_freshness.rb +0 -38
- data/lib/llm_cost_tracker/price_registry.rb +0 -144
- data/lib/llm_cost_tracker/price_sync/fetcher.rb +0 -104
- data/lib/llm_cost_tracker/price_sync/registry_diff.rb +0 -51
- data/lib/llm_cost_tracker/price_sync/registry_loader.rb +0 -61
- data/lib/llm_cost_tracker/price_sync/registry_writer.rb +0 -29
- data/lib/llm_cost_tracker/price_sync.rb +0 -144
- data/lib/llm_cost_tracker/report_data.rb +0 -94
- data/lib/llm_cost_tracker/report_formatter.rb +0 -67
- data/lib/llm_cost_tracker/request_url.rb +0 -20
- data/lib/llm_cost_tracker/storage/active_record_backend.rb +0 -167
- data/lib/llm_cost_tracker/storage/active_record_connection_cleanup.rb +0 -13
- data/lib/llm_cost_tracker/storage/active_record_inbox.rb +0 -160
- data/lib/llm_cost_tracker/storage/active_record_period_totals.rb +0 -84
- data/lib/llm_cost_tracker/storage/active_record_rollup_batch.rb +0 -41
- data/lib/llm_cost_tracker/storage/active_record_rollup_upsert_sql.rb +0 -42
- data/lib/llm_cost_tracker/storage/active_record_rollups.rb +0 -146
- data/lib/llm_cost_tracker/storage/active_record_store.rb +0 -145
- data/lib/llm_cost_tracker/storage/writer.rb +0 -35
- data/lib/llm_cost_tracker/stream_capture.rb +0 -7
- data/lib/llm_cost_tracker/stream_collector.rb +0 -199
- data/lib/llm_cost_tracker/tag_accessors.rb +0 -15
- data/lib/llm_cost_tracker/tag_context.rb +0 -52
- data/lib/llm_cost_tracker/tag_key.rb +0 -16
- data/lib/llm_cost_tracker/tag_query.rb +0 -43
- data/lib/llm_cost_tracker/tag_sanitizer.rb +0 -81
- data/lib/llm_cost_tracker/tag_sql.rb +0 -34
- data/lib/llm_cost_tracker/tags_column.rb +0 -105
- data/lib/llm_cost_tracker/unknown_pricing.rb +0 -54
- data/lib/llm_cost_tracker/usage_breakdown.rb +0 -30
- data/lib/llm_cost_tracker/value_helpers.rb +0 -40
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "active_record_adapter"
|
|
4
|
-
|
|
5
|
-
module LlmCostTracker
|
|
6
|
-
module TagsColumn
|
|
7
|
-
USAGE_BREAKDOWN_COLUMNS = %w[
|
|
8
|
-
cache_read_input_tokens
|
|
9
|
-
cache_write_input_tokens
|
|
10
|
-
hidden_output_tokens
|
|
11
|
-
].freeze
|
|
12
|
-
|
|
13
|
-
USAGE_BREAKDOWN_COST_COLUMNS = %w[
|
|
14
|
-
cache_read_input_cost
|
|
15
|
-
cache_write_input_cost
|
|
16
|
-
].freeze
|
|
17
|
-
|
|
18
|
-
def reset_column_information
|
|
19
|
-
remove_instance_variable(:@lct_schema_capabilities) if instance_variable_defined?(:@lct_schema_capabilities)
|
|
20
|
-
|
|
21
|
-
super
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def tags_json_column?
|
|
25
|
-
capabilities = lct_schema_capabilities
|
|
26
|
-
|
|
27
|
-
capabilities.fetch(:tags_jsonb) || capabilities.fetch(:tags_mysql_json)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def tags_jsonb_column?
|
|
31
|
-
lct_schema_capabilities.fetch(:tags_jsonb)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def tags_mysql_json_column?
|
|
35
|
-
lct_schema_capabilities.fetch(:tags_mysql_json)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def latency_column?
|
|
39
|
-
lct_schema_capabilities.fetch(:latency)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def stream_column?
|
|
43
|
-
lct_schema_capabilities.fetch(:stream)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def usage_source_column?
|
|
47
|
-
lct_schema_capabilities.fetch(:usage_source)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def provider_response_id_column?
|
|
51
|
-
lct_schema_capabilities.fetch(:provider_response_id)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def pricing_mode_column?
|
|
55
|
-
lct_schema_capabilities.fetch(:pricing_mode)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def usage_breakdown_columns?
|
|
59
|
-
lct_schema_capabilities.fetch(:usage_breakdown)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def usage_breakdown_cost_columns?
|
|
63
|
-
lct_schema_capabilities.fetch(:usage_breakdown_cost)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def lct_schema_capabilities
|
|
69
|
-
columns = columns_hash
|
|
70
|
-
adapter_name = connection.adapter_name
|
|
71
|
-
cache = @lct_schema_capabilities
|
|
72
|
-
|
|
73
|
-
return cache.fetch(:values) if cache && cache.fetch(:columns).equal?(columns) &&
|
|
74
|
-
cache.fetch(:adapter_name) == adapter_name
|
|
75
|
-
|
|
76
|
-
values = build_lct_schema_capabilities(columns, adapter_name)
|
|
77
|
-
@lct_schema_capabilities = { columns: columns, adapter_name: adapter_name, values: values }
|
|
78
|
-
values
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def build_lct_schema_capabilities(columns, adapter_name)
|
|
82
|
-
ActiveRecordAdapter.ensure_supported!(adapter_name)
|
|
83
|
-
|
|
84
|
-
tag_column = columns["tags"]
|
|
85
|
-
tags_jsonb = tag_column && (tag_column.type == :jsonb || tag_column.sql_type.to_s.downcase == "jsonb")
|
|
86
|
-
tags_mysql_json =
|
|
87
|
-
tag_column &&
|
|
88
|
-
!tags_jsonb &&
|
|
89
|
-
tag_column.type == :json &&
|
|
90
|
-
ActiveRecordAdapter.mysql?(adapter_name)
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
tags_jsonb: tags_jsonb ? true : false,
|
|
94
|
-
tags_mysql_json: tags_mysql_json ? true : false,
|
|
95
|
-
latency: columns.key?("latency_ms"),
|
|
96
|
-
stream: columns.key?("stream"),
|
|
97
|
-
usage_source: columns.key?("usage_source"),
|
|
98
|
-
provider_response_id: columns.key?("provider_response_id"),
|
|
99
|
-
pricing_mode: columns.key?("pricing_mode"),
|
|
100
|
-
usage_breakdown: USAGE_BREAKDOWN_COLUMNS.all? { |column| columns.key?(column) },
|
|
101
|
-
usage_breakdown_cost: USAGE_BREAKDOWN_COST_COLUMNS.all? { |column| columns.key?(column) }
|
|
102
|
-
}
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "monitor"
|
|
4
|
-
|
|
5
|
-
require_relative "logging"
|
|
6
|
-
|
|
7
|
-
module LlmCostTracker
|
|
8
|
-
class UnknownPricing
|
|
9
|
-
MUTEX = Monitor.new
|
|
10
|
-
|
|
11
|
-
class << self
|
|
12
|
-
def handle!(model)
|
|
13
|
-
model = normalized_model_name(model)
|
|
14
|
-
|
|
15
|
-
case behavior
|
|
16
|
-
when :ignore
|
|
17
|
-
nil
|
|
18
|
-
when :warn
|
|
19
|
-
warn_missing(model)
|
|
20
|
-
when :raise
|
|
21
|
-
raise UnknownPricingError.new(model: model)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def reset!
|
|
26
|
-
MUTEX.synchronize { @warned_models = Set.new }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def normalized_model_name(model)
|
|
32
|
-
model.to_s.empty? ? "unknown" : model.to_s
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def warn_missing(model)
|
|
36
|
-
should_warn = MUTEX.synchronize do
|
|
37
|
-
@warned_models ||= Set.new
|
|
38
|
-
@warned_models.add?(model)
|
|
39
|
-
end
|
|
40
|
-
return unless should_warn
|
|
41
|
-
|
|
42
|
-
Logging.warn(
|
|
43
|
-
"No pricing configured for model #{model.inspect}. " \
|
|
44
|
-
"Cost and budget guardrails will be skipped for this event. " \
|
|
45
|
-
"Add a pricing_overrides entry or set unknown_pricing_behavior."
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def behavior
|
|
50
|
-
LlmCostTracker.configuration.unknown_pricing_behavior
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LlmCostTracker
|
|
4
|
-
UsageBreakdown = Data.define(
|
|
5
|
-
:input_tokens,
|
|
6
|
-
:cache_read_input_tokens,
|
|
7
|
-
:cache_write_input_tokens,
|
|
8
|
-
:output_tokens,
|
|
9
|
-
:hidden_output_tokens
|
|
10
|
-
) do
|
|
11
|
-
def self.build(input_tokens:, output_tokens:, cache_read_input_tokens: 0,
|
|
12
|
-
cache_write_input_tokens: 0, hidden_output_tokens: 0)
|
|
13
|
-
new(
|
|
14
|
-
input_tokens: input_tokens.to_i,
|
|
15
|
-
cache_read_input_tokens: cache_read_input_tokens.to_i,
|
|
16
|
-
cache_write_input_tokens: cache_write_input_tokens.to_i,
|
|
17
|
-
output_tokens: output_tokens.to_i,
|
|
18
|
-
hidden_output_tokens: hidden_output_tokens.to_i
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def total_tokens
|
|
23
|
-
input_tokens + cache_read_input_tokens + cache_write_input_tokens + output_tokens
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def to_h
|
|
27
|
-
super.merge(total_tokens: total_tokens).compact
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LlmCostTracker
|
|
4
|
-
module ValueHelpers
|
|
5
|
-
class << self
|
|
6
|
-
def deep_dup(value)
|
|
7
|
-
case value
|
|
8
|
-
when Hash
|
|
9
|
-
value.each_with_object({}) do |(key, nested_value), duplicated|
|
|
10
|
-
duplicated[deep_dup(key)] = deep_dup(nested_value)
|
|
11
|
-
end
|
|
12
|
-
when Array
|
|
13
|
-
value.map { |nested_value| deep_dup(nested_value) }
|
|
14
|
-
when String
|
|
15
|
-
value.dup
|
|
16
|
-
else
|
|
17
|
-
value
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def deep_freeze(value)
|
|
22
|
-
case value
|
|
23
|
-
when Hash
|
|
24
|
-
value.each do |key, nested_value|
|
|
25
|
-
deep_freeze(key)
|
|
26
|
-
deep_freeze(nested_value)
|
|
27
|
-
end
|
|
28
|
-
value.frozen? ? value : value.freeze
|
|
29
|
-
when Array
|
|
30
|
-
value.each { |nested_value| deep_freeze(nested_value) }
|
|
31
|
-
value.frozen? ? value : value.freeze
|
|
32
|
-
when String
|
|
33
|
-
value.frozen? ? value : value.freeze
|
|
34
|
-
else
|
|
35
|
-
value
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|