timeprice 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +18 -2
- data/data/cpi/eu.json +406 -403
- data/data/cpi/jp.json +78 -75
- data/data/cpi/uk.json +513 -510
- data/data/cpi/us.json +488 -485
- data/data/cpi/vn.json +342 -339
- data/data/fx/usd/1999.json +23 -7
- data/data/fx/usd/2000.json +23 -7
- data/data/fx/usd/2001.json +23 -7
- data/data/fx/usd/2002.json +23 -7
- data/data/fx/usd/2003.json +23 -7
- data/data/fx/usd/2004.json +23 -7
- data/data/fx/usd/2005.json +23 -7
- data/data/fx/usd/2006.json +23 -7
- data/data/fx/usd/2007.json +23 -7
- data/data/fx/usd/2008.json +23 -7
- data/data/fx/usd/2009.json +23 -7
- data/data/fx/usd/2010.json +23 -7
- data/data/fx/usd/2011.json +23 -7
- data/data/fx/usd/2012.json +23 -7
- data/data/fx/usd/2013.json +23 -7
- data/data/fx/usd/2014.json +23 -7
- data/data/fx/usd/2015.json +23 -7
- data/data/fx/usd/2016.json +23 -7
- data/data/fx/usd/2017.json +23 -7
- data/data/fx/usd/2018.json +23 -7
- data/data/fx/usd/2019.json +23 -7
- data/data/fx/usd/2020.json +23 -7
- data/data/fx/usd/2021.json +23 -7
- data/data/fx/usd/2022.json +23 -7
- data/data/fx/usd/2023.json +23 -7
- data/data/fx/usd/2024.json +23 -7
- data/data/fx/usd/2025.json +24 -5
- data/data/fx/usd/2026.json +24 -5
- data/data/fx/usd/_annual.json +145 -0
- data/data/manifest.json +90 -0
- data/lib/timeprice/compare.rb +1 -1
- data/lib/timeprice/cpi_lookup.rb +2 -2
- data/lib/timeprice/data_loader.rb +42 -7
- data/lib/timeprice/errors.rb +4 -4
- data/lib/timeprice/exchange.rb +8 -8
- data/lib/timeprice/inflation.rb +2 -2
- data/lib/timeprice/sources/coverage.rb +27 -32
- data/lib/timeprice/supported.rb +39 -22
- data/lib/timeprice/version.rb +1 -1
- data/lib/timeprice.rb +2 -2
- metadata +3 -15
- data/data/fx/usd/1983.json +0 -11
- data/data/fx/usd/1986.json +0 -11
- data/data/fx/usd/1987.json +0 -11
- data/data/fx/usd/1988.json +0 -11
- data/data/fx/usd/1989.json +0 -11
- data/data/fx/usd/1990.json +0 -11
- data/data/fx/usd/1991.json +0 -11
- data/data/fx/usd/1992.json +0 -11
- data/data/fx/usd/1993.json +0 -11
- data/data/fx/usd/1994.json +0 -11
- data/data/fx/usd/1995.json +0 -11
- data/data/fx/usd/1996.json +0 -11
- data/data/fx/usd/1997.json +0 -11
- data/data/fx/usd/1998.json +0 -11
data/data/manifest.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"countries": [
|
|
3
|
+
{
|
|
4
|
+
"code": "EU",
|
|
5
|
+
"cpi_file": "cpi/eu.json",
|
|
6
|
+
"currency": "EUR",
|
|
7
|
+
"granularities": [
|
|
8
|
+
"monthly",
|
|
9
|
+
"annual"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"code": "JP",
|
|
14
|
+
"cpi_file": "cpi/jp.json",
|
|
15
|
+
"currency": "JPY",
|
|
16
|
+
"granularities": [
|
|
17
|
+
"annual"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"code": "UK",
|
|
22
|
+
"cpi_file": "cpi/uk.json",
|
|
23
|
+
"currency": "GBP",
|
|
24
|
+
"granularities": [
|
|
25
|
+
"monthly",
|
|
26
|
+
"annual"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"code": "US",
|
|
31
|
+
"cpi_file": "cpi/us.json",
|
|
32
|
+
"currency": "USD",
|
|
33
|
+
"granularities": [
|
|
34
|
+
"monthly",
|
|
35
|
+
"annual"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"code": "VN",
|
|
40
|
+
"cpi_file": "cpi/vn.json",
|
|
41
|
+
"currency": "VND",
|
|
42
|
+
"granularities": [
|
|
43
|
+
"monthly",
|
|
44
|
+
"annual"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"fx": {
|
|
49
|
+
"annual_file": "fx/usd/_annual.json",
|
|
50
|
+
"base": "USD",
|
|
51
|
+
"currencies": [
|
|
52
|
+
"EUR",
|
|
53
|
+
"GBP",
|
|
54
|
+
"JPY",
|
|
55
|
+
"VND"
|
|
56
|
+
],
|
|
57
|
+
"daily_years": [
|
|
58
|
+
1999,
|
|
59
|
+
2000,
|
|
60
|
+
2001,
|
|
61
|
+
2002,
|
|
62
|
+
2003,
|
|
63
|
+
2004,
|
|
64
|
+
2005,
|
|
65
|
+
2006,
|
|
66
|
+
2007,
|
|
67
|
+
2008,
|
|
68
|
+
2009,
|
|
69
|
+
2010,
|
|
70
|
+
2011,
|
|
71
|
+
2012,
|
|
72
|
+
2013,
|
|
73
|
+
2014,
|
|
74
|
+
2015,
|
|
75
|
+
2016,
|
|
76
|
+
2017,
|
|
77
|
+
2018,
|
|
78
|
+
2019,
|
|
79
|
+
2020,
|
|
80
|
+
2021,
|
|
81
|
+
2022,
|
|
82
|
+
2023,
|
|
83
|
+
2024,
|
|
84
|
+
2025,
|
|
85
|
+
2026
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"generated_at": "2026-05-11",
|
|
89
|
+
"schema_version": 3
|
|
90
|
+
}
|
data/lib/timeprice/compare.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Timeprice
|
|
|
38
38
|
# accepts a {Point} or a 2-tuple like `["USD", "2010"]` or `["USD", "2010-06"]`
|
|
39
39
|
# @param to [Timeprice::Point, Array(String, String)] destination point
|
|
40
40
|
# @return [CompareResult]
|
|
41
|
-
# @raise [UnsupportedCurrency] if either currency is not in {Supported
|
|
41
|
+
# @raise [UnsupportedCurrency] if either currency is not in {Supported.currencies}
|
|
42
42
|
def run(amount:, from:, to:)
|
|
43
43
|
from_point, to_point, to_country = resolve_points(from, to)
|
|
44
44
|
|
data/lib/timeprice/cpi_lookup.rb
CHANGED
|
@@ -14,8 +14,8 @@ module Timeprice
|
|
|
14
14
|
class CpiLookup
|
|
15
15
|
def initialize(data)
|
|
16
16
|
@data = data
|
|
17
|
-
@monthly = data
|
|
18
|
-
@annual = data
|
|
17
|
+
@monthly = data.dig("series", "monthly") || {}
|
|
18
|
+
@annual = data.dig("series", "annual") || {}
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# @param key [String] "YYYY" or "YYYY-MM"
|
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "errors"
|
|
5
|
-
require_relative "supported"
|
|
6
5
|
|
|
7
6
|
module Timeprice
|
|
8
7
|
# Loads and caches the bundled JSON data files. Override the search root
|
|
9
8
|
# by setting `TIMEPRICE_DATA_ROOT` in the environment or assigning
|
|
10
9
|
# {DataLoader.data_root=}.
|
|
11
10
|
module DataLoader
|
|
12
|
-
SUPPORTED_SCHEMA_VERSION =
|
|
11
|
+
SUPPORTED_SCHEMA_VERSION = 3
|
|
13
12
|
|
|
14
13
|
DEFAULT_DATA_ROOT = File.expand_path("../../data", __dir__)
|
|
15
14
|
|
|
16
15
|
class << self
|
|
17
|
-
# @return [String] absolute path to the directory containing `cpi
|
|
16
|
+
# @return [String] absolute path to the directory containing `cpi/`, `fx/`, `manifest.json`.
|
|
18
17
|
def data_root
|
|
19
18
|
ENV["TIMEPRICE_DATA_ROOT"] || @data_root || DEFAULT_DATA_ROOT
|
|
20
19
|
end
|
|
@@ -32,19 +31,36 @@ module Timeprice
|
|
|
32
31
|
def clear_cache!
|
|
33
32
|
@cpi_cache = {}
|
|
34
33
|
@fx_cache = {}
|
|
34
|
+
@manifest_cache = {}
|
|
35
|
+
@annual_fallback_cache = {}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Load the top-level manifest describing the bundled dataset.
|
|
39
|
+
# @return [Hash]
|
|
40
|
+
# @raise [DataNotFound] if `manifest.json` is missing
|
|
41
|
+
def load_manifest
|
|
42
|
+
manifest_cache[data_root] ||= begin
|
|
43
|
+
path = File.join(data_root, "manifest.json")
|
|
44
|
+
unless File.exist?(path)
|
|
45
|
+
raise DataNotFound, "manifest.json missing (looked in #{path}). " \
|
|
46
|
+
"Check TIMEPRICE_DATA_ROOT or reinstall the gem."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
parse_with_schema(path)
|
|
50
|
+
end
|
|
35
51
|
end
|
|
36
52
|
|
|
37
53
|
# Load the CPI series for a supported country.
|
|
38
54
|
# @param country [String]
|
|
39
|
-
# @return [Hash] parsed JSON with "
|
|
40
|
-
# @raise [UnsupportedCountry] if `country` is not in {Supported
|
|
55
|
+
# @return [Hash] parsed JSON with "series" / "index" / "provenance" / "providers"
|
|
56
|
+
# @raise [UnsupportedCountry] if `country` is not in {Supported.countries}
|
|
41
57
|
# @raise [DataNotFound] if the file is missing
|
|
42
58
|
# @raise [UnsupportedSchemaVersion] if the file uses a future schema
|
|
43
59
|
def load_cpi(country)
|
|
44
60
|
key = country.to_s.downcase
|
|
45
61
|
code = country.to_s.upcase
|
|
46
62
|
cpi_cache[[data_root, key]] ||= begin
|
|
47
|
-
raise UnsupportedCountry, code unless Supported
|
|
63
|
+
raise UnsupportedCountry, code unless Supported.country?(code)
|
|
48
64
|
|
|
49
65
|
path = File.join(data_root, "cpi", "#{key}.json")
|
|
50
66
|
unless File.exist?(path)
|
|
@@ -58,7 +74,7 @@ module Timeprice
|
|
|
58
74
|
|
|
59
75
|
# Load the FX rates for a year.
|
|
60
76
|
# @param year [Integer, String]
|
|
61
|
-
# @return [Hash] parsed JSON with
|
|
77
|
+
# @return [Hash] parsed JSON with `rates` (and optional `annual`) blocks
|
|
62
78
|
# @raise [DataNotFound] if the per-year file is missing
|
|
63
79
|
def load_fx_year(year)
|
|
64
80
|
key = year.to_i
|
|
@@ -70,6 +86,17 @@ module Timeprice
|
|
|
70
86
|
end
|
|
71
87
|
end
|
|
72
88
|
|
|
89
|
+
# Load the sparse historical FX annual-only fallback file, if present.
|
|
90
|
+
# Returns nil when no fallback file ships with this data root.
|
|
91
|
+
# @return [Hash, nil]
|
|
92
|
+
def load_fx_annual_fallback
|
|
93
|
+
return @annual_fallback_cache[data_root] if @annual_fallback_cache&.key?(data_root)
|
|
94
|
+
|
|
95
|
+
@annual_fallback_cache ||= {}
|
|
96
|
+
path = File.join(data_root, "fx", "usd", "_annual.json")
|
|
97
|
+
@annual_fallback_cache[data_root] = File.exist?(path) ? parse_with_schema(path) : nil
|
|
98
|
+
end
|
|
99
|
+
|
|
73
100
|
private
|
|
74
101
|
|
|
75
102
|
def cpi_cache
|
|
@@ -80,6 +107,10 @@ module Timeprice
|
|
|
80
107
|
@fx_cache ||= {}
|
|
81
108
|
end
|
|
82
109
|
|
|
110
|
+
def manifest_cache
|
|
111
|
+
@manifest_cache ||= {}
|
|
112
|
+
end
|
|
113
|
+
|
|
83
114
|
def parse_with_schema(path)
|
|
84
115
|
data = JSON.parse(File.read(path))
|
|
85
116
|
version = data["schema_version"]
|
|
@@ -90,3 +121,7 @@ module Timeprice
|
|
|
90
121
|
end
|
|
91
122
|
end
|
|
92
123
|
end
|
|
124
|
+
|
|
125
|
+
# Supported is loaded by the top-level entry point. Referenced lazily inside
|
|
126
|
+
# load_cpi to avoid a require cycle (Supported reads the manifest via DataLoader).
|
|
127
|
+
require_relative "supported" unless defined?(Timeprice::Supported)
|
data/lib/timeprice/errors.rb
CHANGED
|
@@ -7,23 +7,23 @@ module Timeprice
|
|
|
7
7
|
# to handle anything the gem can throw at you.
|
|
8
8
|
class Error < StandardError; end
|
|
9
9
|
|
|
10
|
-
# Raised when a country code is not in {Supported
|
|
10
|
+
# Raised when a country code is not in {Supported.countries}.
|
|
11
11
|
class UnsupportedCountry < Error
|
|
12
12
|
attr_reader :country
|
|
13
13
|
|
|
14
14
|
def initialize(country)
|
|
15
15
|
@country = country
|
|
16
|
-
super("Unsupported country: #{country.inspect} (supported: #{Supported
|
|
16
|
+
super("Unsupported country: #{country.inspect} (supported: #{Supported.countries.join(", ")})")
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Raised when a currency code is not in {Supported
|
|
20
|
+
# Raised when a currency code is not in {Supported.currencies}.
|
|
21
21
|
class UnsupportedCurrency < Error
|
|
22
22
|
attr_reader :currency
|
|
23
23
|
|
|
24
24
|
def initialize(currency)
|
|
25
25
|
@currency = currency
|
|
26
|
-
super("Unsupported currency: #{currency.inspect} (supported: #{Supported
|
|
26
|
+
super("Unsupported currency: #{currency.inspect} (supported: #{Supported.currencies.join(", ")})")
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
data/lib/timeprice/exchange.rb
CHANGED
|
@@ -33,8 +33,8 @@ module Timeprice
|
|
|
33
33
|
def convert(amount:, from:, to:, date:)
|
|
34
34
|
from = from.to_s.upcase
|
|
35
35
|
to = to.to_s.upcase
|
|
36
|
-
raise UnsupportedCurrency, from unless Supported
|
|
37
|
-
raise UnsupportedCurrency, to unless Supported
|
|
36
|
+
raise UnsupportedCurrency, from unless Supported.currency?(from)
|
|
37
|
+
raise UnsupportedCurrency, to unless Supported.currency?(to)
|
|
38
38
|
|
|
39
39
|
d = parse_date(date)
|
|
40
40
|
|
|
@@ -82,7 +82,7 @@ module Timeprice
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# Walk back up to MAX_FALLBACK_DAYS to find a daily rate; if none, fall
|
|
85
|
-
# back to the
|
|
85
|
+
# back to data/fx/usd/_annual.json (the single source of annual FX truth).
|
|
86
86
|
# Returns [rate, effective_date, granularity].
|
|
87
87
|
def lookup_usd_base(currency, d)
|
|
88
88
|
(0..MAX_FALLBACK_DAYS).each do |offset|
|
|
@@ -108,12 +108,12 @@ module Timeprice
|
|
|
108
108
|
raise DataNotFound, "No FX rate for USD->#{currency} on or before #{d}"
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
# Consult
|
|
111
|
+
# Consult data/fx/usd/_annual.json. Returns Float or nil.
|
|
112
112
|
def annual_fallback(currency, year)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
fallback = DataLoader.load_fx_annual_fallback
|
|
114
|
+
return nil unless fallback
|
|
115
|
+
|
|
116
|
+
fallback.dig("annual", year.to_s, currency)&.to_f
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def parse_date(date)
|
data/lib/timeprice/inflation.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Timeprice
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# CPI-based inflation adjustment for the {Supported
|
|
24
|
+
# CPI-based inflation adjustment for the {Supported.countries} list.
|
|
25
25
|
module Inflation
|
|
26
26
|
module_function
|
|
27
27
|
|
|
@@ -32,7 +32,7 @@ module Timeprice
|
|
|
32
32
|
# @param amount [Numeric]
|
|
33
33
|
# @param from [String] source date ("YYYY" or "YYYY-MM")
|
|
34
34
|
# @param to [String] target date ("YYYY" or "YYYY-MM")
|
|
35
|
-
# @param country [String] country code (see {Supported
|
|
35
|
+
# @param country [String] country code (see {Supported.countries})
|
|
36
36
|
# @return [InflationResult]
|
|
37
37
|
# @raise [UnsupportedCountry] if `country` is not supported
|
|
38
38
|
# @raise [DataNotFound] if no CPI data covers the requested period
|
|
@@ -5,9 +5,10 @@ require_relative "../data_loader"
|
|
|
5
5
|
|
|
6
6
|
module Timeprice
|
|
7
7
|
module Sources
|
|
8
|
-
# Computes coverage strings for bundled data sources at runtime
|
|
9
|
-
#
|
|
10
|
-
# a pure data table
|
|
8
|
+
# Computes coverage strings for bundled data sources at runtime by reading
|
|
9
|
+
# the structured `provenance` blocks in v3 data files. The Sources
|
|
10
|
+
# attribution registry stays a pure data table; Coverage is the only
|
|
11
|
+
# place that touches the filesystem.
|
|
11
12
|
module Coverage
|
|
12
13
|
module_function
|
|
13
14
|
|
|
@@ -25,46 +26,40 @@ module Timeprice
|
|
|
25
26
|
|
|
26
27
|
def cpi(country)
|
|
27
28
|
data = DataLoader.load_cpi(country)
|
|
28
|
-
monthly = (
|
|
29
|
-
annual = (
|
|
29
|
+
monthly = data.dig("series", "monthly") || {}
|
|
30
|
+
annual = data.dig("series", "annual") || {}
|
|
30
31
|
parts = []
|
|
31
|
-
parts << "monthly #{monthly.
|
|
32
|
-
parts << "annual #{annual.
|
|
32
|
+
parts << "monthly #{monthly.keys.min}..#{monthly.keys.max} (#{monthly.size})" if monthly.any?
|
|
33
|
+
parts << "annual #{annual.keys.min}..#{annual.keys.max} (#{annual.size})" if annual.any?
|
|
33
34
|
parts.join(", ")
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def fx(id)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def fx_years
|
|
44
|
-
Dir[File.join(fx_root, "*.json")].map { |f| File.basename(f, ".json").to_i }.sort
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def vnd_summary(years)
|
|
48
|
-
with_vnd = years.select { |y| year_has_currency?(y, %w[VND]) }
|
|
49
|
-
return "no VND data" if with_vnd.empty?
|
|
50
|
-
|
|
51
|
-
"USD↔VND #{with_vnd.first}..#{with_vnd.last}"
|
|
38
|
+
case id
|
|
39
|
+
when "fx_ecb" then ecb_summary
|
|
40
|
+
when "fx_vnd" then vnd_summary
|
|
41
|
+
else "n/a"
|
|
42
|
+
end
|
|
52
43
|
end
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
# Frankfurter (ECB) → daily EUR/GBP/JPY in per-year files. Range derived
|
|
46
|
+
# from the manifest's `daily_years` list.
|
|
47
|
+
def ecb_summary
|
|
48
|
+
years = DataLoader.load_manifest.dig("fx", "daily_years") || []
|
|
49
|
+
return "no ECB data" if years.empty?
|
|
57
50
|
|
|
58
|
-
"USD↔EUR/GBP/JPY daily #{
|
|
51
|
+
"USD↔EUR/GBP/JPY daily #{years.first}..#{years.last}"
|
|
59
52
|
end
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
# All annual FX (today only VND) lives in data/fx/usd/_annual.json.
|
|
55
|
+
def vnd_summary
|
|
56
|
+
fallback = DataLoader.load_fx_annual_fallback
|
|
57
|
+
years = (fallback&.dig("annual") || {})
|
|
58
|
+
.select { |_y, ccy_hash| ccy_hash.key?("VND") }
|
|
59
|
+
.keys.map(&:to_i).sort
|
|
60
|
+
return "no VND data" if years.empty?
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
File.join(DataLoader.data_root, "fx", "usd")
|
|
62
|
+
"USD↔VND #{years.first}..#{years.last}"
|
|
68
63
|
end
|
|
69
64
|
end
|
|
70
65
|
end
|
data/lib/timeprice/supported.rb
CHANGED
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Timeprice
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Supported country and currency codes, derived from `data/manifest.json`.
|
|
5
|
+
# Adding a country = drop a CPI file + regenerate the manifest. No code
|
|
6
|
+
# change required.
|
|
6
7
|
#
|
|
7
8
|
# Everything that needs to know "which currency pairs with which CPI series"
|
|
8
|
-
# must read it from here
|
|
9
|
-
# when a new country was added in one place and forgotten in the other.
|
|
9
|
+
# must read it from here.
|
|
10
10
|
module Supported
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# Currencies with no minor unit — formatted as whole numbers. This is
|
|
12
|
+
# ISO 4217 metadata, not bundled data, so it stays hardcoded.
|
|
13
|
+
ZERO_DECIMAL_CURRENCIES = %w[JPY VND].freeze
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
"US" => "USD",
|
|
16
|
-
"UK" => "GBP",
|
|
17
|
-
"EU" => "EUR",
|
|
18
|
-
"JP" => "JPY",
|
|
19
|
-
"VN" => "VND",
|
|
20
|
-
}.freeze
|
|
15
|
+
module_function
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
# @return [Array<String>] frozen list of supported country codes.
|
|
18
|
+
def countries
|
|
19
|
+
manifest_countries.map { |c| c["code"] }.freeze
|
|
20
|
+
end
|
|
23
21
|
|
|
24
|
-
#
|
|
25
|
-
|
|
22
|
+
# @return [Array<String>] frozen list of supported currency codes
|
|
23
|
+
# (the FX base USD plus every currency the manifest declares).
|
|
24
|
+
def currencies
|
|
25
|
+
base = DataLoader.load_manifest.dig("fx", "base")
|
|
26
|
+
([base] + DataLoader.load_manifest.dig("fx", "currencies")).uniq.freeze
|
|
27
|
+
end
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
# @return [Hash{String=>String}] country code → currency code.
|
|
30
|
+
def country_to_currency
|
|
31
|
+
manifest_countries.to_h { |c| [c["code"], c["currency"]] }.freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Hash{String=>String}] currency code → country code.
|
|
35
|
+
def currency_to_country
|
|
36
|
+
country_to_currency.invert.freeze
|
|
37
|
+
end
|
|
28
38
|
|
|
29
39
|
# ISO 4217 minor-unit count for a currency. Falls back to 2 for unknown
|
|
30
40
|
# codes so callers can still render *some* value rather than crashing.
|
|
31
|
-
#
|
|
32
41
|
# @param currency [String]
|
|
33
42
|
# @return [Integer]
|
|
34
43
|
def decimals_for(currency)
|
|
@@ -38,25 +47,33 @@ module Timeprice
|
|
|
38
47
|
# @param country [String]
|
|
39
48
|
# @return [Boolean]
|
|
40
49
|
def country?(country)
|
|
41
|
-
|
|
50
|
+
countries.include?(country.to_s.upcase)
|
|
42
51
|
end
|
|
43
52
|
|
|
44
53
|
# @param currency [String]
|
|
45
54
|
# @return [Boolean]
|
|
46
55
|
def currency?(currency)
|
|
47
|
-
|
|
56
|
+
currencies.include?(currency.to_s.upcase)
|
|
48
57
|
end
|
|
49
58
|
|
|
50
59
|
# @param currency [String] ISO 4217 code (e.g. "USD")
|
|
51
60
|
# @return [String, nil] country code, or nil if unsupported
|
|
52
61
|
def country_for_currency(currency)
|
|
53
|
-
|
|
62
|
+
currency_to_country[currency.to_s.upcase]
|
|
54
63
|
end
|
|
55
64
|
|
|
56
65
|
# @param country [String] country code (e.g. "US")
|
|
57
66
|
# @return [String, nil] currency code, or nil if unsupported
|
|
58
67
|
def currency_for_country(country)
|
|
59
|
-
|
|
68
|
+
country_to_currency[country.to_s.upcase]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class << self
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def manifest_countries
|
|
75
|
+
DataLoader.load_manifest["countries"] || []
|
|
76
|
+
end
|
|
60
77
|
end
|
|
61
78
|
end
|
|
62
79
|
end
|
data/lib/timeprice/version.rb
CHANGED
data/lib/timeprice.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "timeprice/version"
|
|
4
|
+
require_relative "timeprice/data_loader"
|
|
4
5
|
require_relative "timeprice/supported"
|
|
5
6
|
require_relative "timeprice/errors"
|
|
6
7
|
require_relative "timeprice/point"
|
|
7
|
-
require_relative "timeprice/data_loader"
|
|
8
8
|
require_relative "timeprice/inflation"
|
|
9
9
|
require_relative "timeprice/exchange"
|
|
10
10
|
require_relative "timeprice/compare"
|
|
@@ -30,7 +30,7 @@ module Timeprice
|
|
|
30
30
|
# @param amount [Numeric] the original amount
|
|
31
31
|
# @param from [String] source date as "YYYY" or "YYYY-MM"
|
|
32
32
|
# @param to [String] target date as "YYYY" or "YYYY-MM"
|
|
33
|
-
# @param country [String] country code from {Supported
|
|
33
|
+
# @param country [String] country code from {Supported.countries}
|
|
34
34
|
# @return [InflationResult]
|
|
35
35
|
# @raise [UnsupportedCountry] if `country` is not supported
|
|
36
36
|
# @raise [DataNotFound] if no CPI point covers `from` or `to`
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -112,20 +112,6 @@ files:
|
|
|
112
112
|
- data/cpi/uk.json
|
|
113
113
|
- data/cpi/us.json
|
|
114
114
|
- data/cpi/vn.json
|
|
115
|
-
- data/fx/usd/1983.json
|
|
116
|
-
- data/fx/usd/1986.json
|
|
117
|
-
- data/fx/usd/1987.json
|
|
118
|
-
- data/fx/usd/1988.json
|
|
119
|
-
- data/fx/usd/1989.json
|
|
120
|
-
- data/fx/usd/1990.json
|
|
121
|
-
- data/fx/usd/1991.json
|
|
122
|
-
- data/fx/usd/1992.json
|
|
123
|
-
- data/fx/usd/1993.json
|
|
124
|
-
- data/fx/usd/1994.json
|
|
125
|
-
- data/fx/usd/1995.json
|
|
126
|
-
- data/fx/usd/1996.json
|
|
127
|
-
- data/fx/usd/1997.json
|
|
128
|
-
- data/fx/usd/1998.json
|
|
129
115
|
- data/fx/usd/1999.json
|
|
130
116
|
- data/fx/usd/2000.json
|
|
131
117
|
- data/fx/usd/2001.json
|
|
@@ -154,6 +140,8 @@ files:
|
|
|
154
140
|
- data/fx/usd/2024.json
|
|
155
141
|
- data/fx/usd/2025.json
|
|
156
142
|
- data/fx/usd/2026.json
|
|
143
|
+
- data/fx/usd/_annual.json
|
|
144
|
+
- data/manifest.json
|
|
157
145
|
- exe/timeprice
|
|
158
146
|
- lib/timeprice.rb
|
|
159
147
|
- lib/timeprice/cli.rb
|
data/data/fx/usd/1983.json
DELETED
data/data/fx/usd/1986.json
DELETED
data/data/fx/usd/1987.json
DELETED
data/data/fx/usd/1988.json
DELETED
data/data/fx/usd/1989.json
DELETED
data/data/fx/usd/1990.json
DELETED
data/data/fx/usd/1991.json
DELETED
data/data/fx/usd/1992.json
DELETED