# frozen_string_literal: true require "net/http" require "ox" require "provider/adapters/adapter" class Provider module Adapters # Central Bank of Uruguay (Banco Central del Uruguay). # Fetches official exchange rates in UYU via SOAP web service. # Rates are stored with base=foreign_currency, quote=UYU. class BCU < Adapter ENDPOINT = URI("https://cotizaciones.bcu.gub.uy/wscotizaciones/servlet/awsbcucotizaciones") # Map BCU numeric currency codes to ISO 4217 codes CURRENCY_MAPPING = { "2225" => "USD", # DLS. USA BILLETE "1111" => "EUR", # EURO "1000" => "BRL", # REAL "0500" => "ARS", # PESO ARGENTINO "2309" => "CAD", # DOLAR CANADIENSE "1300" => "CLP", # PESO CHILENO "4150" => "CNY", # YUAN RENMIMBI "5500" => "COP", # PESO COLOMBIANO "1800" => "DKK", # CORONA DANESA "5100" => "HKD", # DOLAR HONG KONG "4300" => "HUF", # FORINT HUNGARO "5700" => "INR", # RUPIA INDIA "2700" => "GBP", # LIBRA ESTERLINA "4900" => "ISK", # CORONA ISLANDESA "3600" => "JPY", # YEN "5300" => "KRW", # WON "5600" => "MYR", # RINGGIT "4200" => "MXN", # PESO MEXICANO "4600" => "NOK", # CORONA NORUEGA "1490" => "NZD", # DOL. NEOZELANDES "4800" => "PYG", # GUARANI "4000" => "PEN", # NVO.SOL PERUANO "5400" => "RUB", # RUBLO "1620" => "ZAR", # RAND SUDAFRICANO "5800" => "SEK", # CORONA SUECA "5900" => "CHF", # FRANCO SUIZO "4400" => "TRY", # LIRA TURCA }.freeze class << self def backfill_range = 365 end def fetch(after: nil, upto: nil) end_date = upto || Date.today dataset = [] currencies = CURRENCY_MAPPING.values.uniq currencies.each do |iso_code| body = soap_request(iso_code, after, end_date) response = Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: true) do |http| req = Net::HTTP::Post.new(ENDPOINT) req["Content-Type"] = "text/xml; charset=utf-8" req.body = body http.request(req) end dataset.concat(parse(response.body)) sleep(1) end dataset end def parse(xml) doc = Ox.load(xml) doc.locate("*/datoscotizaciones.dato").filter_map do |dato| date_str = dato.locate("Fecha").first&.text moneda_str = dato.locate("Moneda").first&.text tcc_str = dato.locate("TCC").first&.text tcv_str = dato.locate("TCV").first&.text next unless date_str && moneda_str && tcc_str && tcv_str # Map the currency code to ISO base_currency = CURRENCY_MAPPING[moneda_str] next unless base_currency # Skip UYU self-reference next if base_currency == "UYU" date = Date.parse(date_str) tcc = Float(tcc_str) tcv = Float(tcv_str) next if tcc.zero? || tcv.zero? # Calculate midpoint rate rate = (tcc + tcv) / 2.0 { date:, base: base_currency, quote: "UYU", rate: } end end private def soap_request(iso_code, start_date, end_date) currency_code = CURRENCY_MAPPING.key(iso_code) return "" unless currency_code <<~XML #{currency_code} #{start_date.strftime("%Y-%m-%d")} #{end_date.strftime("%Y-%m-%d")} 0 XML end end end end