timeprice 0.6.0 → 0.7.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +33 -2
  4. data/data/cpi/au.json +419 -0
  5. data/data/cpi/ca.json +1501 -0
  6. data/data/cpi/cn.json +487 -0
  7. data/data/cpi/eu.json +1 -1
  8. data/data/cpi/jp.json +1 -1
  9. data/data/cpi/kr.json +549 -0
  10. data/data/cpi/ru.json +487 -0
  11. data/data/cpi/uk.json +1 -1
  12. data/data/cpi/us.json +1 -1
  13. data/data/cpi/vn.json +26 -26
  14. data/data/fx/usd/1999.json +1042 -262
  15. data/data/fx/usd/2000.json +1274 -258
  16. data/data/fx/usd/2001.json +1277 -257
  17. data/data/fx/usd/2002.json +1282 -258
  18. data/data/fx/usd/2003.json +1282 -258
  19. data/data/fx/usd/2004.json +1302 -262
  20. data/data/fx/usd/2005.json +1292 -260
  21. data/data/fx/usd/2006.json +1282 -258
  22. data/data/fx/usd/2007.json +1282 -258
  23. data/data/fx/usd/2008.json +1287 -259
  24. data/data/fx/usd/2009.json +1287 -259
  25. data/data/fx/usd/2010.json +1297 -261
  26. data/data/fx/usd/2011.json +1292 -260
  27. data/data/fx/usd/2012.json +1287 -259
  28. data/data/fx/usd/2013.json +1282 -258
  29. data/data/fx/usd/2014.json +1282 -258
  30. data/data/fx/usd/2015.json +1287 -259
  31. data/data/fx/usd/2016.json +1292 -260
  32. data/data/fx/usd/2017.json +1282 -258
  33. data/data/fx/usd/2018.json +1282 -258
  34. data/data/fx/usd/2019.json +1282 -258
  35. data/data/fx/usd/2020.json +1292 -260
  36. data/data/fx/usd/2021.json +1297 -261
  37. data/data/fx/usd/2022.json +1292 -260
  38. data/data/fx/usd/2023.json +1282 -258
  39. data/data/fx/usd/2024.json +1287 -259
  40. data/data/fx/usd/2025.json +1282 -258
  41. data/data/fx/usd/2026.json +457 -92
  42. data/data/fx/usd/_annual.json +46 -1
  43. data/data/manifest.json +155 -7
  44. data/lib/timeprice/cli.rb +3 -3
  45. data/lib/timeprice/compare.rb +36 -3
  46. data/lib/timeprice/cpi_lookup.rb +4 -4
  47. data/lib/timeprice/data_loader.rb +8 -17
  48. data/lib/timeprice/date.rb +62 -0
  49. data/lib/timeprice/exchange.rb +49 -23
  50. data/lib/timeprice/inflation.rb +12 -4
  51. data/lib/timeprice/metadata.rb +121 -0
  52. data/lib/timeprice/metadata_snapshot.rb +23 -0
  53. data/lib/timeprice/point.rb +11 -3
  54. data/lib/timeprice/schema.rb +78 -0
  55. data/lib/timeprice/version.rb +1 -1
  56. data/lib/timeprice.rb +14 -1
  57. metadata +24 -1
@@ -5,6 +5,7 @@ require_relative "errors"
5
5
  require_relative "data_loader"
6
6
  require_relative "supported"
7
7
  require_relative "granularity"
8
+ require_relative "date"
8
9
 
9
10
  module Timeprice
10
11
  ExchangeResult = Data.define(
@@ -15,6 +16,11 @@ module Timeprice
15
16
  # Handles identity (USD→USD), direct lookup, inverse, and triangulation
16
17
  # through USD. Weekend/holiday dates fall back up to {MAX_FALLBACK_DAYS}
17
18
  # days to the nearest prior trading day.
19
+ #
20
+ # @api private
21
+ # The supported public entry point is {Timeprice.exchange}. Direct
22
+ # references will move to `Timeprice::Internal::Exchange` in a future
23
+ # release.
18
24
  module Exchange
19
25
  BASE = "USD"
20
26
  MAX_FALLBACK_DAYS = 7
@@ -33,8 +39,8 @@ module Timeprice
33
39
  def convert(amount:, from:, to:, date:)
34
40
  from = from.to_s.upcase
35
41
  to = to.to_s.upcase
36
- raise UnsupportedCurrency, from unless Supported.currency?(from)
37
- raise UnsupportedCurrency, to unless Supported.currency?(to)
42
+ fail UnsupportedCurrency, from unless Supported.currency?(from)
43
+ fail UnsupportedCurrency, to unless Supported.currency?(to)
38
44
 
39
45
  d = parse_date(date)
40
46
 
@@ -69,18 +75,32 @@ module Timeprice
69
75
  rate, eff, gran = lookup_usd_base(from, d)
70
76
  [1.0 / rate, eff, gran]
71
77
  else
72
- # Triangulation: from → USD → to, both legs at the same effective date.
73
- usd_to_from, eff_a, gran_a = lookup_usd_base(from, d)
74
- usd_to_to, eff_b, gran_b = lookup_usd_base(to, d)
75
- if eff_a != eff_b
76
- raise DataNotFound,
77
- "FX triangulation date mismatch for #{from}->#{to} on #{d}: " \
78
- "USD->#{from} resolved #{eff_a}, USD->#{to} resolved #{eff_b}"
79
- end
80
- [usd_to_to / usd_to_from, eff_a, Granularity.merge(gran_a, gran_b)]
78
+ # Triangulation: from → USD → to. Daily legs must agree on the
79
+ # effective date; an annual leg is valid for any date in its year, so
80
+ # we adopt the daily leg's date and let Granularity.merge demote.
81
+ rate_a, *leg_a = lookup_usd_base(from, d)
82
+ rate_b, *leg_b = lookup_usd_base(to, d)
83
+ eff = reconcile_triangulation_dates(from, to, d, leg_a, leg_b)
84
+ [rate_b / rate_a, eff, Granularity.merge(leg_a[1], leg_b[1])]
81
85
  end
82
86
  end
83
87
 
88
+ # Pick a single effective date for a triangulated rate. Daily legs must
89
+ # agree; an annual leg is year-wide so it adopts the daily leg's date.
90
+ # When both legs are annual we fall back to the requested date.
91
+ def reconcile_triangulation_dates(from, to, d, leg_a, leg_b)
92
+ eff_a, gran_a = leg_a
93
+ eff_b, gran_b = leg_b
94
+ return eff_a if eff_a == eff_b
95
+ return d if gran_a == Granularity::ANNUAL && gran_b == Granularity::ANNUAL
96
+ return eff_b if gran_a == Granularity::ANNUAL
97
+ return eff_a if gran_b == Granularity::ANNUAL
98
+
99
+ fail DataNotFound,
100
+ "FX triangulation date mismatch for #{from}->#{to} on #{d}: " \
101
+ "USD->#{from} resolved #{eff_a}, USD->#{to} resolved #{eff_b}"
102
+ end
103
+
84
104
  # Walk back up to MAX_FALLBACK_DAYS to find a daily rate; if none, fall
85
105
  # back to data/fx/usd/_annual.json (the single source of annual FX truth).
86
106
  # Returns [rate, effective_date, granularity].
@@ -105,7 +125,7 @@ module Timeprice
105
125
  annual_rate = annual_fallback(currency, d.year)
106
126
  return [annual_rate, d, Granularity::ANNUAL] if annual_rate
107
127
 
108
- raise DataNotFound, "No FX rate for USD->#{currency} on or before #{d}"
128
+ fail DataNotFound, "No FX rate for USD->#{currency} on or before #{d}"
109
129
  end
110
130
 
111
131
  # Consult data/fx/usd/_annual.json. Returns Float or nil.
@@ -118,20 +138,26 @@ module Timeprice
118
138
 
119
139
  def parse_date(date)
120
140
  case date
121
- when Date then date
141
+ when ::Date
142
+ date
143
+ when Timeprice::Date
144
+ require_daily!(date)
145
+ ::Date.new(date.year, date.month, date.day)
122
146
  when String
123
- unless date.match?(/\A\d{4}-\d{2}-\d{2}\z/)
124
- raise ArgumentError, "Invalid date format: #{date.inspect} (use YYYY-MM-DD)"
125
- end
126
-
127
- begin
128
- Date.parse(date)
129
- rescue Date::Error
130
- raise ArgumentError, "Invalid date: #{date.inspect} is not a real calendar date"
131
- end
147
+ parsed = Timeprice::Date.coerce(date)
148
+ require_daily!(parsed)
149
+ ::Date.new(parsed.year, parsed.month, parsed.day)
132
150
  else
133
- raise ArgumentError, "Invalid date: #{date.inspect}"
151
+ fail ArgumentError, "Invalid date: #{date.inspect}"
134
152
  end
153
+ rescue ::Date::Error
154
+ raise ArgumentError, "Invalid date: #{date.inspect} is not a real calendar date"
155
+ end
156
+
157
+ def require_daily!(date)
158
+ return if date.granularity == :daily
159
+
160
+ fail ArgumentError, "Invalid date: Exchange needs YYYY-MM-DD, got #{date}"
135
161
  end
136
162
  end
137
163
  end
@@ -4,6 +4,7 @@ require_relative "errors"
4
4
  require_relative "data_loader"
5
5
  require_relative "cpi_lookup"
6
6
  require_relative "granularity"
7
+ require_relative "date"
7
8
 
8
9
  module Timeprice
9
10
  # Value object returned by Inflation.adjust. See {Granularity} for the set
@@ -22,6 +23,11 @@ module Timeprice
22
23
  end
23
24
 
24
25
  # CPI-based inflation adjustment for the {Supported.countries} list.
26
+ #
27
+ # @api private
28
+ # The supported public entry point is {Timeprice.inflation}. Direct
29
+ # references to this module will move to `Timeprice::Internal::Inflation`
30
+ # in a future release.
25
31
  module Inflation
26
32
  module_function
27
33
 
@@ -37,16 +43,18 @@ module Timeprice
37
43
  # @raise [UnsupportedCountry] if `country` is not supported
38
44
  # @raise [DataNotFound] if no CPI data covers the requested period
39
45
  def adjust(amount:, from:, to:, country:)
46
+ from = Timeprice::Date.coerce(from)
47
+ to = Timeprice::Date.coerce(to)
40
48
  lookup = CpiLookup.new(DataLoader.load_cpi(country))
41
- from_point = lookup.at(from)
42
- to_point = lookup.at(to)
49
+ from_point = lookup.at(from.to_s)
50
+ to_point = lookup.at(to.to_s)
43
51
 
44
52
  ratio = to_point.value.to_f / from_point.value
45
53
  InflationResult.new(
46
54
  amount: amount.to_f * ratio,
47
55
  original_amount: amount.to_f,
48
- from: from,
49
- to: to,
56
+ from: from.to_s,
57
+ to: to.to_s,
50
58
  country: country.to_s.upcase,
51
59
  from_index: from_point.value,
52
60
  to_index: to_point.value,
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "data_loader"
4
+ require_relative "supported"
5
+ require_relative "version"
6
+ require_relative "metadata_snapshot"
7
+
8
+ module Timeprice
9
+ # Describes the bundled dataset so external surfaces (the website, other
10
+ # tools) can render dropdowns, date pickers, and version pills without
11
+ # hardcoding country lists, currency lists, or date ranges.
12
+ #
13
+ # See {Timeprice.metadata} for the public entry point.
14
+ #
15
+ # @api private
16
+ # Direct references will move to `Timeprice::Internal::Metadata` in a
17
+ # future release.
18
+ module Metadata
19
+ # ISO 3166-style display names for the countries shipped today.
20
+ COUNTRY_NAMES = {
21
+ "AU" => "Australia",
22
+ "CA" => "Canada",
23
+ "CN" => "China",
24
+ "EU" => "Eurozone",
25
+ "JP" => "Japan",
26
+ "KR" => "South Korea",
27
+ "RU" => "Russia",
28
+ "UK" => "United Kingdom",
29
+ "US" => "United States",
30
+ "VN" => "Vietnam",
31
+ }.freeze
32
+
33
+ # ISO 4217 display names for the currencies shipped today.
34
+ CURRENCY_NAMES = {
35
+ "AUD" => "Australian dollar",
36
+ "CAD" => "Canadian dollar",
37
+ "CNY" => "Chinese yuan",
38
+ "EUR" => "Euro",
39
+ "GBP" => "British pound",
40
+ "JPY" => "Japanese yen",
41
+ "KRW" => "South Korean won",
42
+ "RUB" => "Russian ruble",
43
+ "USD" => "US dollar",
44
+ "VND" => "Vietnamese dong",
45
+ }.freeze
46
+
47
+ module_function
48
+
49
+ # Build the metadata snapshot.
50
+ # @return [MetadataSnapshot]
51
+ def build
52
+ manifest = DataLoader.load_manifest
53
+ countries = (manifest["countries"] || []).map { |c| country_entry(c) }
54
+ currencies = Supported.currencies.map { |code| { code: code, name: CURRENCY_NAMES[code] || code } }
55
+ MetadataSnapshot.new(
56
+ version: VERSION,
57
+ generated_at: manifest["generated_at"],
58
+ countries: deep_freeze(countries),
59
+ currencies: deep_freeze(currencies),
60
+ fx: deep_freeze(fx_entry(manifest))
61
+ )
62
+ end
63
+
64
+ # Range info comes from the manifest (`cpi_ranges`), pre-computed at
65
+ # manifest generation time. Falls back to walking the CPI file for any
66
+ # country missing the field — older manifests, or local data roots
67
+ # produced by hand.
68
+ def country_entry(country)
69
+ code = country["code"]
70
+ ranges = country["cpi_ranges"] || derive_cpi_ranges(code)
71
+ per_granularity = ranges.each_with_object({}) do |(gran, range), acc|
72
+ acc[gran.to_sym] = { min: range["min"], max: range["max"] }
73
+ end
74
+ {
75
+ code: code,
76
+ name: COUNTRY_NAMES[code] || code,
77
+ currency: country["currency"],
78
+ granularities: country["granularities"] || per_granularity.keys.map(&:to_s),
79
+ cpi: per_granularity,
80
+ }
81
+ end
82
+
83
+ def derive_cpi_ranges(code)
84
+ cpi = DataLoader.load_cpi(code)
85
+ series = cpi["series"] || {}
86
+ series.each_with_object({}) do |(granularity, points), acc|
87
+ next unless points.is_a?(Hash) && !points.empty?
88
+
89
+ keys = points.keys.sort
90
+ acc[granularity] = { "min" => keys.first, "max" => keys.last }
91
+ end
92
+ end
93
+
94
+ # Bounds come from the manifest (`fx.daily_min`/`fx.daily_max`). Older
95
+ # manifests without those keys: peek at the earliest/latest year files.
96
+ def fx_entry(manifest)
97
+ fx = manifest["fx"] || {}
98
+ base = fx["base"]
99
+ years = fx["daily_years"] || []
100
+ return { base: base, daily_min: nil, daily_max: nil } if years.empty?
101
+
102
+ daily_min = fx["daily_min"]
103
+ daily_max = fx["daily_max"]
104
+ if daily_min.nil? || daily_max.nil?
105
+ first = DataLoader.load_fx_year(years.min)
106
+ last = DataLoader.load_fx_year(years.max)
107
+ daily_min ||= (first["rates"] || {}).keys.min
108
+ daily_max ||= (last["rates"] || {}).keys.max
109
+ end
110
+ { base: base, daily_min: daily_min, daily_max: daily_max }
111
+ end
112
+
113
+ def deep_freeze(value)
114
+ case value
115
+ when Hash then value.each_value { |v| deep_freeze(v) }.freeze
116
+ when Array then value.each { |v| deep_freeze(v) }.freeze
117
+ else value.frozen? ? value : value.freeze
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Timeprice
6
+ # Frozen value object describing the bundled dataset: version, refresh
7
+ # date, country list with CPI ranges, currency list with display names,
8
+ # and FX coverage. Replaces the previous Hash return shape on
9
+ # {Timeprice.metadata}.
10
+ #
11
+ # `[]`, `to_h`, and `to_json` are kept compatible with the old Hash
12
+ # interface so downstream consumers (the website, this gem's specs)
13
+ # don't need a coordinated rewrite.
14
+ MetadataSnapshot = Data.define(:version, :generated_at, :countries, :currencies, :fx) do
15
+ def [](key)
16
+ to_h[key]
17
+ end
18
+
19
+ def to_json(*args)
20
+ to_h.to_json(*args)
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "date"
4
+
3
5
  module Timeprice
4
6
  # A (currency, date) pair used as input to {Timeprice.compare}.
5
7
  #
@@ -13,6 +15,12 @@ module Timeprice
13
15
  # Timeprice::Point.coerce(["USD", "2010"])
14
16
  # Timeprice::Point.coerce(["2010", "USD"])
15
17
  Point = Data.define(:currency, :date) do
18
+ # Canonical constructor. Accepts a stdlib-string or Timeprice::Date
19
+ # for the date argument; stores the canonical string form.
20
+ def self.parse(currency, date)
21
+ new(currency: currency.to_s.upcase, date: Timeprice::Date.coerce(date).to_s)
22
+ end
23
+
16
24
  # Coerce input into a Point. Accepts:
17
25
  # - {Point} (returned as-is)
18
26
  # - 2-element Array of [currency, date] in either order
@@ -28,11 +36,11 @@ module Timeprice
28
36
  a, b = input.map(&:to_s)
29
37
  currency = [a, b].find { |s| s.match?(/\A[A-Za-z]{3}\z/) }
30
38
  date = [a, b].find { |s| s.match?(/\A\d{4}(-\d{2}(-\d{2})?)?\z/) }
31
- raise ArgumentError, malformed_pair_message(input) if currency.nil? || date.nil?
39
+ fail ArgumentError, malformed_pair_message(input) if currency.nil? || date.nil?
32
40
 
33
41
  new(currency: currency.upcase, date: date)
34
42
  else
35
- raise ArgumentError, "Expected Timeprice::Point or [currency, date] tuple, got #{input.inspect}"
43
+ fail ArgumentError, "Expected Timeprice::Point or [currency, date] tuple, got #{input.inspect}"
36
44
  end
37
45
  end
38
46
 
@@ -55,7 +63,7 @@ module Timeprice
55
63
  when /\A\d{4}\z/ then "#{date}-06-30"
56
64
  when /\A\d{4}-\d{2}\z/ then "#{date}-15"
57
65
  when /\A\d{4}-\d{2}-\d{2}\z/ then date.to_s
58
- else raise ArgumentError, "Invalid date for Point: #{date.inspect}"
66
+ else fail ArgumentError, "Invalid date for Point: #{date.inspect}"
59
67
  end
60
68
  end
61
69
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+
5
+ module Timeprice
6
+ # Single source of truth for the on-disk v4 CPI/manifest format. Both the
7
+ # reader ({DataLoader}) and the writer (today: pipeline `CountryFile`)
8
+ # route through here so the schema lives in exactly one place.
9
+ module Schema
10
+ CURRENT_VERSION = 4
11
+ SUPPORTED_VERSIONS = [3, 4].freeze
12
+
13
+ KEY_SCHEMA_VERSION = "schema_version"
14
+ KEY_COUNTRY = "country"
15
+ KEY_INDEX = "index"
16
+ KEY_SERIES = "series"
17
+ KEY_PROVENANCE = "provenance"
18
+ KEY_PROVIDERS = "providers"
19
+
20
+ GRANULARITIES = %i[monthly quarterly annual].freeze
21
+
22
+ BASE_YEAR_RE = /\A(?<period>.+?)=100(?:\s*\(rebased\s+(?<rebased>\d{4}-\d{2}-\d{2})\))?\z/
23
+
24
+ module_function
25
+
26
+ def supported?(version)
27
+ SUPPORTED_VERSIONS.include?(version)
28
+ end
29
+
30
+ def assert_supported!(version, path)
31
+ return if supported?(version)
32
+
33
+ fail UnsupportedSchemaVersion.new(version, path)
34
+ end
35
+
36
+ # Build a CPI payload ready for JSON.dump. Series keys are emitted in a
37
+ # stable order (annual, monthly[, quarterly]) so file diffs stay tight.
38
+ def dump_cpi(country:, base_year:, monthly:, annual:, providers:, provenance:, quarterly: {})
39
+ series = { "annual" => annual, "monthly" => monthly }
40
+ series["quarterly"] = quarterly unless quarterly.empty?
41
+ {
42
+ KEY_SCHEMA_VERSION => CURRENT_VERSION,
43
+ KEY_COUNTRY => country.to_s.upcase,
44
+ KEY_INDEX => serialise_base_year(base_year),
45
+ KEY_SERIES => series,
46
+ KEY_PROVENANCE => provenance,
47
+ KEY_PROVIDERS => providers,
48
+ }
49
+ end
50
+
51
+ # Validate a parsed payload (read from disk) against the schema, then
52
+ # return it unchanged. Raises UnsupportedSchemaVersion if the version
53
+ # field is missing or unknown.
54
+ def load_cpi(parsed, path:)
55
+ assert_supported!(parsed[KEY_SCHEMA_VERSION], path)
56
+ parsed
57
+ end
58
+
59
+ def serialise_base_year(str)
60
+ m = BASE_YEAR_RE.match(str.to_s)
61
+ if m
62
+ { "base_period" => m[:period], "rebased_at" => m[:rebased] }
63
+ else
64
+ { "base_period" => str.to_s, "rebased_at" => nil }
65
+ end
66
+ end
67
+
68
+ def deserialise_base_year(index)
69
+ return nil unless index.is_a?(Hash)
70
+
71
+ period = index["base_period"]
72
+ rebased = index["rebased_at"]
73
+ return nil if period.nil?
74
+
75
+ rebased ? "#{period}=100 (rebased #{rebased})" : "#{period}=100"
76
+ end
77
+ end
78
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Timeprice
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/timeprice.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "timeprice/version"
4
+ require_relative "timeprice/errors"
5
+ require_relative "timeprice/schema"
6
+ require_relative "timeprice/date"
4
7
  require_relative "timeprice/data_loader"
5
8
  require_relative "timeprice/supported"
6
- require_relative "timeprice/errors"
7
9
  require_relative "timeprice/point"
8
10
  require_relative "timeprice/inflation"
9
11
  require_relative "timeprice/exchange"
10
12
  require_relative "timeprice/compare"
11
13
  require_relative "timeprice/sources"
14
+ require_relative "timeprice/metadata"
12
15
 
13
16
  # Offline historical inflation & FX for Ruby.
14
17
  #
@@ -62,4 +65,14 @@ module Timeprice
62
65
  def compare(amount:, from:, to:)
63
66
  Compare.run(amount: amount, from: from, to: to)
64
67
  end
68
+
69
+ # Snapshot describing the bundled dataset: version, refresh date, country
70
+ # list with CPI ranges, currency list with display names, and FX coverage.
71
+ # Intended as the single source of truth for downstream UIs (the website
72
+ # in particular) so dropdowns and date pickers never drift from the data.
73
+ #
74
+ # @return [Hash] frozen, JSON-serialisable
75
+ def metadata
76
+ Metadata.build
77
+ end
65
78
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timeprice
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: lefthook
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.1'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.1'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: rake
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -107,8 +121,13 @@ files:
107
121
  - LICENSE.txt
108
122
  - NOTICE
109
123
  - README.md
124
+ - data/cpi/au.json
125
+ - data/cpi/ca.json
126
+ - data/cpi/cn.json
110
127
  - data/cpi/eu.json
111
128
  - data/cpi/jp.json
129
+ - data/cpi/kr.json
130
+ - data/cpi/ru.json
112
131
  - data/cpi/uk.json
113
132
  - data/cpi/us.json
114
133
  - data/cpi/vn.json
@@ -153,11 +172,15 @@ files:
153
172
  - lib/timeprice/compare.rb
154
173
  - lib/timeprice/cpi_lookup.rb
155
174
  - lib/timeprice/data_loader.rb
175
+ - lib/timeprice/date.rb
156
176
  - lib/timeprice/errors.rb
157
177
  - lib/timeprice/exchange.rb
158
178
  - lib/timeprice/granularity.rb
159
179
  - lib/timeprice/inflation.rb
180
+ - lib/timeprice/metadata.rb
181
+ - lib/timeprice/metadata_snapshot.rb
160
182
  - lib/timeprice/point.rb
183
+ - lib/timeprice/schema.rb
161
184
  - lib/timeprice/sources.rb
162
185
  - lib/timeprice/sources/coverage.rb
163
186
  - lib/timeprice/supported.rb