# frozen_string_literal: true require "json" require "net/http" require "provider/adapters/adapter" class Provider module Adapters # National Bank of Poland. Publishes daily mid-market rates (Table A) for ~32 currencies # and weekly mid-market rates (Table B) for ~150 additional currencies against PLN, # plus a daily gold reference price. Gold values come in PLN per gram and are # normalized here to per troy ounce. class NBP < Adapter TABLE_A_URL = "https://api.nbp.pl/api/exchangerates/tables/A" TABLE_B_URL = "https://api.nbp.pl/api/exchangerates/tables/B" GOLD_URL = "https://api.nbp.pl/api/cenyzlota" def fetch(after: nil, upto: nil) end_date = upto || Date.today dataset = [] each_chunk(after, end_date) do |chunk_start, chunk_end| dataset.concat(fetch_rates(TABLE_A_URL, chunk_start, chunk_end)) dataset.concat(fetch_rates(TABLE_B_URL, chunk_start, chunk_end)) dataset.concat(fetch_gold(chunk_start, chunk_end)) end dataset end def parse(json) data = json.is_a?(String) ? JSON.parse(json) : json data.flat_map do |table| date = Date.parse(table["effectiveDate"]) table["rates"].filter_map do |rate| iso = rate["code"] mid = rate["mid"] next unless iso.match?(/\A[A-Z]{3}\z/) next if mid.nil? || mid.zero? { date:, base: iso, quote: "PLN", rate: mid } end end end def parse_gold(json) data = json.is_a?(String) ? JSON.parse(json) : json data.filter_map do |row| price = row["cena"] next if price.nil? || price.zero? { date: Date.parse(row["data"]), base: "XAU", quote: "PLN", rate: price * GRAMS_PER_TROY_OUNCE } end end private def fetch_rates(table_url, start_date, end_date) url = URI("#{table_url}/#{start_date}/#{end_date}/?format=json") response = Net::HTTP.get_response(url) # This happens if the date range includes no working days return [] if response.is_a?(Net::HTTPNotFound) response.value parse(response.body) end def fetch_gold(start_date, end_date) url = URI("#{GOLD_URL}/#{start_date}/#{end_date}/?format=json") response = Net::HTTP.get_response(url) # This happens if the date range includes no working days return [] if response.is_a?(Net::HTTPNotFound) response.value parse_gold(response.body) end # NBP API limits queries to 93 days per request def each_chunk(start_date, end_date) current = start_date first = true while current <= end_date sleep(0.5) unless first first = false chunk_end = [current + 92, end_date].min yield current, chunk_end current = chunk_end + 1 end end end end end