Module: Timeprice::Inflation

Defined in:
lib/timeprice/inflation.rb

Overview

CPI-based inflation adjustment for the Supported::COUNTRIES list.

Class Method Summary collapse

Class Method Details

.adjust(amount:, from:, to:, country:) ⇒ InflationResult

Adjust ‘amount` from date `from` to date `to` using country CPI.

Dates accept “YYYY” or “YYYY-MM”.

Parameters:

  • amount (Numeric)
  • from (String)

    source date (“YYYY” or “YYYY-MM”)

  • to (String)

    target date (“YYYY” or “YYYY-MM”)

  • country (String)

    country code (see Supported::COUNTRIES)

Returns:

Raises:



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/timeprice/inflation.rb', line 34

def adjust(amount:, from:, to:, country:)
  data = DataLoader.load_cpi(country)
  from_index, from_gran = lookup_index(data, from)
  to_index,   to_gran   = lookup_index(data, to)

  ratio = to_index.to_f / from_index
  InflationResult.new(
    amount: amount.to_f * ratio,
    original_amount: amount.to_f,
    from: from,
    to: to,
    country: country.to_s.upcase,
    from_index: from_index,
    to_index: to_index,
    granularity: merge_granularity(from_gran, to_gran)
  )
end

.lookup_index(data, key) ⇒ Object

Returns [index_value, granularity_symbol]



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/timeprice/inflation.rb', line 64

def lookup_index(data, key)
  key = key.to_s
  monthly = data["monthly"] || {}
  annual  = data["annual"]  || {}

  case key
  when /\A\d{4}-\d{2}\z/
    if monthly.key?(key)
      [monthly[key], :monthly]
    else
      year = key[0, 4]
      raise DataNotFound, missing_cpi_message(key, data, monthly, annual) unless annual.key?(year)

      [annual[year], :annual]

    end
  when /\A\d{4}\z/
    if annual.key?(key)
      [annual[key], :annual]
    else
      months = monthly.select { |k, _| k.start_with?("#{key}-") }
      raise DataNotFound, missing_cpi_message(key, data, monthly, annual) if months.empty?

      avg = months.values.sum.to_f / months.size
      [avg, :annual_from_monthly_avg]
    end
  else
    raise ArgumentError, "Invalid date format: #{key.inspect} (use YYYY or YYYY-MM)"
  end
end

.merge_granularity(a, b) ⇒ Object

If either end fell back to annual_from_monthly_avg, propagate that label; else if either is annual, propagate :annual; else :monthly.



112
113
114
115
116
117
# File 'lib/timeprice/inflation.rb', line 112

def merge_granularity(a, b)
  return :annual_from_monthly_avg if a == :annual_from_monthly_avg || b == :annual_from_monthly_avg
  return :annual if a == :annual || b == :annual

  :monthly
end

.missing_cpi_message(key, data, monthly, annual) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/timeprice/inflation.rb', line 95

def missing_cpi_message(key, data, monthly, annual)
  country = data["country"]
  ranges = []
  if monthly.any?
    ks = monthly.keys.sort
    ranges << "monthly #{ks.first}..#{ks.last}"
  end
  if annual.any?
    ks = annual.keys.sort
    ranges << "annual #{ks.first}..#{ks.last}"
  end
  hint = ranges.empty? ? "" : " (supported: #{ranges.join(", ")})"
  "No CPI data for #{key.inspect} in #{country}#{hint}"
end

.rate(from:, to:, country:) ⇒ Float

Inflation rate as decimal (e.g. 0.42 = 42%).

Parameters:

  • from (String)
  • to (String)
  • country (String)

Returns:

  • (Float)

    decimal rate (positive means inflation, negative deflation)



58
59
60
61
# File 'lib/timeprice/inflation.rb', line 58

def rate(from:, to:, country:)
  result = adjust(amount: 1.0, from: from, to: to, country: country)
  result.amount - 1.0
end