timeprice 0.3.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 +55 -0
- data/DATA_LICENSES.md +2 -1
- data/README.md +20 -4
- data/data/cpi/eu.json +422 -397
- data/data/cpi/jp.json +91 -72
- data/data/cpi/uk.json +529 -504
- data/data/cpi/us.json +507 -476
- data/data/cpi/vn.json +368 -37
- data/data/fx/usd/1999.json +281 -524
- data/data/fx/usd/2000.json +277 -516
- data/data/fx/usd/2001.json +276 -511
- data/data/fx/usd/2002.json +277 -513
- data/data/fx/usd/2003.json +277 -513
- data/data/fx/usd/2004.json +281 -521
- data/data/fx/usd/2005.json +279 -520
- data/data/fx/usd/2006.json +277 -513
- data/data/fx/usd/2007.json +277 -513
- data/data/fx/usd/2008.json +278 -515
- data/data/fx/usd/2009.json +278 -515
- data/data/fx/usd/2010.json +280 -522
- data/data/fx/usd/2011.json +279 -520
- data/data/fx/usd/2012.json +278 -515
- data/data/fx/usd/2013.json +277 -513
- data/data/fx/usd/2014.json +277 -513
- data/data/fx/usd/2015.json +278 -515
- data/data/fx/usd/2016.json +279 -520
- data/data/fx/usd/2017.json +277 -513
- data/data/fx/usd/2018.json +277 -513
- data/data/fx/usd/2019.json +277 -513
- data/data/fx/usd/2020.json +279 -517
- data/data/fx/usd/2021.json +280 -522
- data/data/fx/usd/2022.json +279 -520
- data/data/fx/usd/2023.json +277 -513
- data/data/fx/usd/2024.json +278 -515
- data/data/fx/usd/2025.json +22 -3
- data/data/fx/usd/2026.json +22 -3
- data/data/fx/usd/_annual.json +145 -0
- data/data/manifest.json +90 -0
- data/lib/timeprice/cli/presenters/compare.rb +3 -1
- data/lib/timeprice/cli/presenters/inflation.rb +2 -1
- data/lib/timeprice/compare.rb +3 -2
- data/lib/timeprice/cpi_lookup.rb +9 -7
- data/lib/timeprice/data_loader.rb +42 -7
- data/lib/timeprice/errors.rb +4 -4
- data/lib/timeprice/exchange.rb +34 -17
- data/lib/timeprice/granularity.rb +46 -0
- data/lib/timeprice/inflation.rb +6 -19
- data/lib/timeprice/sources/coverage.rb +27 -32
- data/lib/timeprice/sources.rb +5 -5
- data/lib/timeprice/supported.rb +39 -22
- data/lib/timeprice/version.rb +1 -1
- data/lib/timeprice.rb +2 -2
- metadata +4 -15
- data/data/fx/usd/1983.json +0 -12
- data/data/fx/usd/1986.json +0 -12
- data/data/fx/usd/1987.json +0 -12
- data/data/fx/usd/1988.json +0 -12
- data/data/fx/usd/1989.json +0 -12
- data/data/fx/usd/1990.json +0 -12
- data/data/fx/usd/1991.json +0 -12
- data/data/fx/usd/1992.json +0 -12
- data/data/fx/usd/1993.json +0 -12
- data/data/fx/usd/1994.json +0 -12
- data/data/fx/usd/1995.json +0 -12
- data/data/fx/usd/1996.json +0 -12
- data/data/fx/usd/1997.json +0 -12
- data/data/fx/usd/1998.json +0 -12
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Timeprice
|
|
4
|
+
# Closed set of CPI-resolution granularities and the rules for combining /
|
|
5
|
+
# rendering them. Owns the lattice so callers don't hand-maintain it.
|
|
6
|
+
module Granularity
|
|
7
|
+
DAILY = :daily
|
|
8
|
+
MONTHLY = :monthly
|
|
9
|
+
ANNUAL = :annual
|
|
10
|
+
ANNUAL_FROM_MONTHLY_AVG = :annual_from_monthly_avg
|
|
11
|
+
MONTHLY_FROM_ANNUAL_FALLBACK = :monthly_from_annual_fallback
|
|
12
|
+
|
|
13
|
+
# Most-degraded first — `merge` returns the first match.
|
|
14
|
+
# DAILY is the highest-precision FX tag; MONTHLY is the highest-precision
|
|
15
|
+
# CPI tag. Compare uses merge() across both legs, so the most-degraded
|
|
16
|
+
# tag in either leg wins.
|
|
17
|
+
PRECEDENCE = [
|
|
18
|
+
MONTHLY_FROM_ANNUAL_FALLBACK,
|
|
19
|
+
ANNUAL_FROM_MONTHLY_AVG,
|
|
20
|
+
ANNUAL,
|
|
21
|
+
MONTHLY,
|
|
22
|
+
DAILY,
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
HUMAN_LABELS = {
|
|
26
|
+
DAILY => "daily",
|
|
27
|
+
MONTHLY => "monthly",
|
|
28
|
+
ANNUAL => "annual",
|
|
29
|
+
ANNUAL_FROM_MONTHLY_AVG => "annual (avg of months)",
|
|
30
|
+
MONTHLY_FROM_ANNUAL_FALLBACK => "annual (month unavailable)",
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# Worst-precision-wins merge across two or more endpoint granularities.
|
|
36
|
+
def merge(*tags)
|
|
37
|
+
PRECEDENCE.find { |t| tags.include?(t) } || MONTHLY
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Human-readable label for CLI output. Falls through to the symbol's
|
|
41
|
+
# string form so an unknown tag still renders something.
|
|
42
|
+
def humanize(tag)
|
|
43
|
+
HUMAN_LABELS.fetch(tag, tag.to_s)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/timeprice/inflation.rb
CHANGED
|
@@ -3,15 +3,11 @@
|
|
|
3
3
|
require_relative "errors"
|
|
4
4
|
require_relative "data_loader"
|
|
5
5
|
require_relative "cpi_lookup"
|
|
6
|
+
require_relative "granularity"
|
|
6
7
|
|
|
7
8
|
module Timeprice
|
|
8
|
-
# Value object returned by Inflation.adjust.
|
|
9
|
-
#
|
|
10
|
-
# granularity is one of:
|
|
11
|
-
# :monthly — both ends resolved on monthly data
|
|
12
|
-
# :annual — at least one end resolved on annual data
|
|
13
|
-
# :annual_from_monthly_avg — at least one end was an annual request resolved
|
|
14
|
-
# by averaging 12 months of monthly data
|
|
9
|
+
# Value object returned by Inflation.adjust. See {Granularity} for the set
|
|
10
|
+
# of possible `granularity` values and the worst-precision-wins merge rule.
|
|
15
11
|
InflationResult = Data.define(
|
|
16
12
|
:amount, :original_amount, :from, :to, :country,
|
|
17
13
|
:from_index, :to_index, :granularity
|
|
@@ -25,7 +21,7 @@ module Timeprice
|
|
|
25
21
|
end
|
|
26
22
|
end
|
|
27
23
|
|
|
28
|
-
# CPI-based inflation adjustment for the {Supported
|
|
24
|
+
# CPI-based inflation adjustment for the {Supported.countries} list.
|
|
29
25
|
module Inflation
|
|
30
26
|
module_function
|
|
31
27
|
|
|
@@ -36,7 +32,7 @@ module Timeprice
|
|
|
36
32
|
# @param amount [Numeric]
|
|
37
33
|
# @param from [String] source date ("YYYY" or "YYYY-MM")
|
|
38
34
|
# @param to [String] target date ("YYYY" or "YYYY-MM")
|
|
39
|
-
# @param country [String] country code (see {Supported
|
|
35
|
+
# @param country [String] country code (see {Supported.countries})
|
|
40
36
|
# @return [InflationResult]
|
|
41
37
|
# @raise [UnsupportedCountry] if `country` is not supported
|
|
42
38
|
# @raise [DataNotFound] if no CPI data covers the requested period
|
|
@@ -54,7 +50,7 @@ module Timeprice
|
|
|
54
50
|
country: country.to_s.upcase,
|
|
55
51
|
from_index: from_point.value,
|
|
56
52
|
to_index: to_point.value,
|
|
57
|
-
granularity:
|
|
53
|
+
granularity: Granularity.merge(from_point.granularity, to_point.granularity)
|
|
58
54
|
)
|
|
59
55
|
end
|
|
60
56
|
|
|
@@ -68,14 +64,5 @@ module Timeprice
|
|
|
68
64
|
result = adjust(amount: 1.0, from: from, to: to, country: country)
|
|
69
65
|
result.amount - 1.0
|
|
70
66
|
end
|
|
71
|
-
|
|
72
|
-
# If either end fell back to annual_from_monthly_avg, propagate that label;
|
|
73
|
-
# else if either is annual, propagate :annual; else :monthly.
|
|
74
|
-
def merge_granularity(a, b)
|
|
75
|
-
return :annual_from_monthly_avg if a == :annual_from_monthly_avg || b == :annual_from_monthly_avg
|
|
76
|
-
return :annual if a == :annual || b == :annual
|
|
77
|
-
|
|
78
|
-
:monthly
|
|
79
|
-
end
|
|
80
67
|
end
|
|
81
68
|
end
|
|
@@ -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/sources.rb
CHANGED
|
@@ -48,10 +48,10 @@ module Timeprice
|
|
|
48
48
|
id: "vn_cpi",
|
|
49
49
|
kind: "cpi",
|
|
50
50
|
country: "VN",
|
|
51
|
-
name: "World Bank
|
|
52
|
-
license: "CC BY 4.0",
|
|
53
|
-
license_url: "https://
|
|
54
|
-
attribution: "
|
|
51
|
+
name: "IMF Data Portal CPI dataflow (monthly primary) + World Bank FP.CPI.TOTL (annual fallback)",
|
|
52
|
+
license: "IMF: free reuse with attribution; World Bank: CC BY 4.0",
|
|
53
|
+
license_url: "https://www.imf.org/external/terms.htm",
|
|
54
|
+
attribution: "Sources: IMF Data Portal CPI dataflow; World Bank FP.CPI.TOTL",
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
id: "fx_ecb",
|
|
@@ -66,7 +66,7 @@ module Timeprice
|
|
|
66
66
|
id: "fx_vnd",
|
|
67
67
|
kind: "fx",
|
|
68
68
|
country: "VN",
|
|
69
|
-
name: "World Bank — PA.NUS.FCRF (VND annual average,
|
|
69
|
+
name: "World Bank — PA.NUS.FCRF (VND annual average, annual-granularity fallback)",
|
|
70
70
|
license: "CC BY 4.0",
|
|
71
71
|
license_url: "https://datacatalog.worldbank.org/public-licenses#cc-by",
|
|
72
72
|
attribution: "VND FX: World Bank, PA.NUS.FCRF",
|
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
|
|
@@ -167,6 +155,7 @@ files:
|
|
|
167
155
|
- lib/timeprice/data_loader.rb
|
|
168
156
|
- lib/timeprice/errors.rb
|
|
169
157
|
- lib/timeprice/exchange.rb
|
|
158
|
+
- lib/timeprice/granularity.rb
|
|
170
159
|
- lib/timeprice/inflation.rb
|
|
171
160
|
- lib/timeprice/point.rb
|
|
172
161
|
- lib/timeprice/sources.rb
|
data/data/fx/usd/1983.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1983-01-02": {
|
|
5
|
-
"VND": 1.0
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1983
|
|
12
|
-
}
|
data/data/fx/usd/1986.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1986-01-02": {
|
|
5
|
-
"VND": 22.94
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1986
|
|
12
|
-
}
|
data/data/fx/usd/1987.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1987-01-02": {
|
|
5
|
-
"VND": 78.95
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1987
|
|
12
|
-
}
|
data/data/fx/usd/1988.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1988-01-02": {
|
|
5
|
-
"VND": 611.65
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1988
|
|
12
|
-
}
|
data/data/fx/usd/1989.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1989-01-02": {
|
|
5
|
-
"VND": 4501.69
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1989
|
|
12
|
-
}
|
data/data/fx/usd/1990.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1990-01-02": {
|
|
5
|
-
"VND": 6537.6
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1990
|
|
12
|
-
}
|
data/data/fx/usd/1991.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1991-01-02": {
|
|
5
|
-
"VND": 10121.89
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1991
|
|
12
|
-
}
|
data/data/fx/usd/1992.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1992-01-02": {
|
|
5
|
-
"VND": 11202.19
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1992
|
|
12
|
-
}
|
data/data/fx/usd/1993.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1993-01-02": {
|
|
5
|
-
"VND": 10640.96
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1993
|
|
12
|
-
}
|
data/data/fx/usd/1994.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1994-01-02": {
|
|
5
|
-
"VND": 10965.67
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1994
|
|
12
|
-
}
|
data/data/fx/usd/1995.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1995-01-02": {
|
|
5
|
-
"VND": 11038.25
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1995
|
|
12
|
-
}
|
data/data/fx/usd/1996.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1996-01-02": {
|
|
5
|
-
"VND": 11032.58
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1996
|
|
12
|
-
}
|
data/data/fx/usd/1997.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1997-01-02": {
|
|
5
|
-
"VND": 11683.33
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1997
|
|
12
|
-
}
|
data/data/fx/usd/1998.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"base": "USD",
|
|
3
|
-
"rates": {
|
|
4
|
-
"1998-01-02": {
|
|
5
|
-
"VND": 13268.0
|
|
6
|
-
}
|
|
7
|
-
},
|
|
8
|
-
"schema_version": 1,
|
|
9
|
-
"source": "Frankfurter (ECB) for EUR/GBP/JPY; World Bank PA.NUS.FCRF for VND (annual avg, broadcast to every day in year)",
|
|
10
|
-
"updated_at": "2026-05-11",
|
|
11
|
-
"year": 1998
|
|
12
|
-
}
|