Module: Timeprice::Metadata Private

Defined in:
lib/timeprice/metadata.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Describes the bundled dataset so external surfaces (the website, other tools) can render dropdowns, date pickers, and version pills without hardcoding country lists, currency lists, or date ranges.

See metadata for the public entry point.

Direct references will move to ‘Timeprice::Internal::Metadata` in a future release.

Constant Summary collapse

COUNTRY_NAMES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

ISO 3166-style display names for the countries shipped today.

{
  "AU" => "Australia",
  "CA" => "Canada",
  "CN" => "China",
  "EU" => "Eurozone",
  "JP" => "Japan",
  "KR" => "South Korea",
  "RU" => "Russia",
  "UK" => "United Kingdom",
  "US" => "United States",
  "VN" => "Vietnam",
}.freeze
CURRENCY_NAMES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

ISO 4217 display names for the currencies shipped today.

{
  "AUD" => "Australian dollar",
  "CAD" => "Canadian dollar",
  "CNY" => "Chinese yuan",
  "EUR" => "Euro",
  "GBP" => "British pound",
  "JPY" => "Japanese yen",
  "KRW" => "South Korean won",
  "RUB" => "Russian ruble",
  "USD" => "US dollar",
  "VND" => "Vietnamese dong",
}.freeze

Class Method Summary collapse

Class Method Details

.buildMetadataSnapshot

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build the metadata snapshot.

Returns:



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/timeprice/metadata.rb', line 51

def build
  manifest = DataLoader.load_manifest
  countries = (manifest["countries"] || []).map { |c| country_entry(c) }
  currencies = Supported.currencies.map { |code| { code: code, name: CURRENCY_NAMES[code] || code } }
  MetadataSnapshot.new(
    version: VERSION,
    generated_at: manifest["generated_at"],
    countries: deep_freeze(countries),
    currencies: deep_freeze(currencies),
    fx: deep_freeze(fx_entry(manifest))
  )
end

.country_entry(country) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Range info comes from the manifest (‘cpi_ranges`), pre-computed at manifest generation time. Falls back to walking the CPI file for any country missing the field — older manifests, or local data roots produced by hand.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/timeprice/metadata.rb', line 68

def country_entry(country)
  code = country["code"]
  ranges = country["cpi_ranges"] || derive_cpi_ranges(code)
  per_granularity = ranges.each_with_object({}) do |(gran, range), acc|
    acc[gran.to_sym] = { min: range["min"], max: range["max"] }
  end
  {
    code: code,
    name: COUNTRY_NAMES[code] || code,
    currency: country["currency"],
    granularities: country["granularities"] || per_granularity.keys.map(&:to_s),
    cpi: per_granularity,
  }
end

.deep_freeze(value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



113
114
115
116
117
118
119
# File 'lib/timeprice/metadata.rb', line 113

def deep_freeze(value)
  case value
  when Hash  then value.each_value { |v| deep_freeze(v) }.freeze
  when Array then value.each { |v| deep_freeze(v) }.freeze
  else value.frozen? ? value : value.freeze
  end
end

.derive_cpi_ranges(code) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



83
84
85
86
87
88
89
90
91
92
# File 'lib/timeprice/metadata.rb', line 83

def derive_cpi_ranges(code)
  cpi = DataLoader.load_cpi(code)
  series = cpi["series"] || {}
  series.each_with_object({}) do |(granularity, points), acc|
    next unless points.is_a?(Hash) && !points.empty?

    keys = points.keys.sort
    acc[granularity] = { "min" => keys.first, "max" => keys.last }
  end
end

.fx_entry(manifest) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Bounds come from the manifest (‘fx.daily_min`/`fx.daily_max`). Older manifests without those keys: peek at the earliest/latest year files.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/timeprice/metadata.rb', line 96

def fx_entry(manifest)
  fx = manifest["fx"] || {}
  base = fx["base"]
  years = fx["daily_years"] || []
  return { base: base, daily_min: nil, daily_max: nil } if years.empty?

  daily_min = fx["daily_min"]
  daily_max = fx["daily_max"]
  if daily_min.nil? || daily_max.nil?
    first = DataLoader.load_fx_year(years.min)
    last  = DataLoader.load_fx_year(years.max)
    daily_min ||= (first["rates"] || {}).keys.min
    daily_max ||= (last["rates"] || {}).keys.max
  end
  { base: base, daily_min: daily_min, daily_max: daily_max }
end