frankfurter/lib/provider/adapters/bcu.rb

128 lines
4.2 KiB
Ruby
Raw Permalink Normal View History

2026-05-21 01:38:14 -06:00
# 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
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cot="Cotiza">
<soapenv:Header />
<soapenv:Body>
<cot:wsbcucotizaciones.Execute>
<cot:Entrada>
<cot:Moneda>
<cot:item>#{currency_code}</cot:item>
</cot:Moneda>
<cot:FechaDesde>#{start_date.strftime("%Y-%m-%d")}</cot:FechaDesde>
<cot:FechaHasta>#{end_date.strftime("%Y-%m-%d")}</cot:FechaHasta>
<cot:Grupo>0</cot:Grupo>
</cot:Entrada>
</cot:wsbcucotizaciones.Execute>
</soapenv:Body>
</soapenv:Envelope>
XML
end
end
end
end