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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/DATA_LICENSES.md +2 -1
  4. data/README.md +20 -4
  5. data/data/cpi/eu.json +422 -397
  6. data/data/cpi/jp.json +91 -72
  7. data/data/cpi/uk.json +529 -504
  8. data/data/cpi/us.json +507 -476
  9. data/data/cpi/vn.json +368 -37
  10. data/data/fx/usd/1999.json +281 -524
  11. data/data/fx/usd/2000.json +277 -516
  12. data/data/fx/usd/2001.json +276 -511
  13. data/data/fx/usd/2002.json +277 -513
  14. data/data/fx/usd/2003.json +277 -513
  15. data/data/fx/usd/2004.json +281 -521
  16. data/data/fx/usd/2005.json +279 -520
  17. data/data/fx/usd/2006.json +277 -513
  18. data/data/fx/usd/2007.json +277 -513
  19. data/data/fx/usd/2008.json +278 -515
  20. data/data/fx/usd/2009.json +278 -515
  21. data/data/fx/usd/2010.json +280 -522
  22. data/data/fx/usd/2011.json +279 -520
  23. data/data/fx/usd/2012.json +278 -515
  24. data/data/fx/usd/2013.json +277 -513
  25. data/data/fx/usd/2014.json +277 -513
  26. data/data/fx/usd/2015.json +278 -515
  27. data/data/fx/usd/2016.json +279 -520
  28. data/data/fx/usd/2017.json +277 -513
  29. data/data/fx/usd/2018.json +277 -513
  30. data/data/fx/usd/2019.json +277 -513
  31. data/data/fx/usd/2020.json +279 -517
  32. data/data/fx/usd/2021.json +280 -522
  33. data/data/fx/usd/2022.json +279 -520
  34. data/data/fx/usd/2023.json +277 -513
  35. data/data/fx/usd/2024.json +278 -515
  36. data/data/fx/usd/2025.json +22 -3
  37. data/data/fx/usd/2026.json +22 -3
  38. data/data/fx/usd/_annual.json +145 -0
  39. data/data/manifest.json +90 -0
  40. data/lib/timeprice/cli/presenters/compare.rb +3 -1
  41. data/lib/timeprice/cli/presenters/inflation.rb +2 -1
  42. data/lib/timeprice/compare.rb +3 -2
  43. data/lib/timeprice/cpi_lookup.rb +9 -7
  44. data/lib/timeprice/data_loader.rb +42 -7
  45. data/lib/timeprice/errors.rb +4 -4
  46. data/lib/timeprice/exchange.rb +34 -17
  47. data/lib/timeprice/granularity.rb +46 -0
  48. data/lib/timeprice/inflation.rb +6 -19
  49. data/lib/timeprice/sources/coverage.rb +27 -32
  50. data/lib/timeprice/sources.rb +5 -5
  51. data/lib/timeprice/supported.rb +39 -22
  52. data/lib/timeprice/version.rb +1 -1
  53. data/lib/timeprice.rb +2 -2
  54. metadata +4 -15
  55. data/data/fx/usd/1983.json +0 -12
  56. data/data/fx/usd/1986.json +0 -12
  57. data/data/fx/usd/1987.json +0 -12
  58. data/data/fx/usd/1988.json +0 -12
  59. data/data/fx/usd/1989.json +0 -12
  60. data/data/fx/usd/1990.json +0 -12
  61. data/data/fx/usd/1991.json +0 -12
  62. data/data/fx/usd/1992.json +0 -12
  63. data/data/fx/usd/1993.json +0 -12
  64. data/data/fx/usd/1994.json +0 -12
  65. data/data/fx/usd/1995.json +0 -12
  66. data/data/fx/usd/1996.json +0 -12
  67. data/data/fx/usd/1997.json +0 -12
  68. 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
@@ -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::COUNTRIES} list.
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::COUNTRIES})
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: merge_granularity(from_point.granularity, to_point.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. All
9
- # filesystem reads happen here so the Sources attribution registry stays
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 = (data["monthly"] || {}).keys.sort
29
- annual = (data["annual"] || {}).keys.sort
29
+ monthly = data.dig("series", "monthly") || {}
30
+ annual = data.dig("series", "annual") || {}
30
31
  parts = []
31
- parts << "monthly #{monthly.first}..#{monthly.last} (#{monthly.size})" unless monthly.empty?
32
- parts << "annual #{annual.first}..#{annual.last} (#{annual.size})" unless annual.empty?
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
- years = fx_years
38
- return "no data" if years.empty?
39
-
40
- id == "fx_vnd" ? vnd_summary(years) : ecb_summary(years)
41
- end
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
- def ecb_summary(years)
55
- ecb_years = years.select { |y| year_has_currency?(y, %w[EUR GBP JPY]) }
56
- return "no ECB data" if ecb_years.empty?
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 #{ecb_years.first}..#{ecb_years.last}"
51
+ "USD↔EUR/GBP/JPY daily #{years.first}..#{years.last}"
59
52
  end
60
53
 
61
- def year_has_currency?(year, codes)
62
- rates = JSON.parse(File.read(File.join(fx_root, "#{year}.json")))["rates"]
63
- rates.any? { |_, v| v.keys.intersect?(codes) }
64
- end
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
- def fx_root
67
- File.join(DataLoader.data_root, "fx", "usd")
62
+ "USD↔VND #{years.first}..#{years.last}"
68
63
  end
69
64
  end
70
65
  end
@@ -48,10 +48,10 @@ module Timeprice
48
48
  id: "vn_cpi",
49
49
  kind: "cpi",
50
50
  country: "VN",
51
- name: "World Bank FP.CPI.TOTL (annual)",
52
- license: "CC BY 4.0",
53
- license_url: "https://datacatalog.worldbank.org/public-licenses#cc-by",
54
- attribution: "Source: World Bank, FP.CPI.TOTL",
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, broadcast daily)",
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",
@@ -1,34 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Timeprice
4
- # Canonical lists of supported country and currency codes, plus the
5
- # bidirectional currency↔country map used by `Compare` and CLI output.
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 — duplicating the map elsewhere has bitten us before
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
- COUNTRIES = %w[US UK EU JP VN].freeze
12
- CURRENCIES = %w[USD GBP EUR JPY VND].freeze
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
- COUNTRY_TO_CURRENCY = {
15
- "US" => "USD",
16
- "UK" => "GBP",
17
- "EU" => "EUR",
18
- "JP" => "JPY",
19
- "VN" => "VND",
20
- }.freeze
15
+ module_function
21
16
 
22
- CURRENCY_TO_COUNTRY = COUNTRY_TO_CURRENCY.invert.freeze
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
- # Currencies with no minor unit formatted as whole numbers.
25
- ZERO_DECIMAL_CURRENCIES = %w[JPY VND].freeze
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
- module_function
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
- COUNTRIES.include?(country.to_s.upcase)
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
- CURRENCIES.include?(currency.to_s.upcase)
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
- CURRENCY_TO_COUNTRY[currency.to_s.upcase]
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
- COUNTRY_TO_CURRENCY[country.to_s.upcase]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Timeprice
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
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::COUNTRIES}
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.3.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
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }