frankfurter/lib/provider/adapters/cbk.rb
BadMark d0a43e357a
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / publish (push) Has been cancelled
CI/CD Pipeline / release (push) Has been cancelled
up
2026-05-21 01:38:14 -06:00

126 lines
3.5 KiB
Ruby

# frozen_string_literal: true
require "json"
require "net/http"
require "provider/adapters/adapter"
class Provider
module Adapters
# Central Bank of Kenya daily exchange rates in KES.
# Fetches from two WPDataTables endpoints: table 32 (2003-2024) and table 193 (2024+).
class CBK < Adapter
BASE_URL = "https://www.centralbank.go.ke/wp-admin/admin-ajax.php"
CURRENCY_MAP = {
"US DOLLAR" => "USD",
"STG POUND" => "GBP",
"EURO" => "EUR",
"JAPANESE YEN" => "JPY",
"JPY" => "JPY",
"SWISS FRANC" => "CHF",
"S FRANC" => "CHF",
"CAN DOLLAR" => "CAD",
"CAN $" => "CAD",
"AUSTRALIAN DOLLAR" => "AUD",
"AUSTRALIAN $" => "AUD",
"INDIAN RUPEE" => "INR",
"IND RUPEE" => "INR",
"SWEDISH KRONA" => "SEK",
"SW KRONER" => "SEK",
"NORWEGIAN KRONE" => "NOK",
"NOR KRONER" => "NOK",
"DAN KRONER" => "DKK",
"CHINESE YUAN" => "CNY",
"S. AFRICAN RAND" => "ZAR",
"SA RAND" => "ZAR",
"AE DIRHAM" => "AED",
"UAE DIRHAM" => "AED",
"HONGKONG DOLLAR" => "HKD",
"SINGAPORE DOLLAR" => "SGD",
"SAUDI RIYAL" => "SAR",
"USHS" => "UGX",
"TSHS" => "TZS",
"RWF" => "RWF",
"BIF" => "BIF",
}.freeze
def fetch(after: nil, upto: nil)
dataset = []
if after.nil? || after < Date.new(2024, 1, 5)
dataset.concat(fetch_table(32))
end
dataset.concat(fetch_table(193))
dataset = dataset.select { |r| r[:date] >= after } if after
dataset = dataset.select { |r| r[:date] <= upto } if upto
dataset
end
def parse(json)
data = json.is_a?(String) ? JSON.parse(json) : json
rows = data["data"]
return [] unless rows.is_a?(Array)
rows.filter_map do |row|
parse_row(row)
end
end
private
def fetch_table(table_id)
uri = URI("#{BASE_URL}?action=get_wdtable&table_id=#{table_id}")
request = Net::HTTP::Post.new(uri)
request.set_form_data("draw" => "1", "start" => "0", "length" => "-1")
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
parse(response.body)
end
def parse_row(row)
date_str, currency_name, *rest = row
return unless date_str && currency_name
date = Date.strptime(date_str.strip, "%d/%m/%Y")
iso, cross_rate = resolve_currency(currency_name.strip)
return unless iso
units = parse_units(currency_name)
rate_value = Float(rest[0])
return if rate_value.zero?
rate_value /= units if units > 1
rate_value = 1.0 / rate_value if cross_rate
{ date:, base: iso, quote: "KES", rate: rate_value }
end
def resolve_currency(name)
upname = name.upcase
# Check for East African cross-rate patterns like "KES / USHS" or "KEN SHILLING / USHS"
if upname.match?(%r{KE[SN][\s/]|KEN SHILLING\s*/})
suffix = upname.split("/").last.strip
iso = CURRENCY_MAP[suffix]
return [iso, true] if iso
end
# Strip unit markers like "(100)" before matching
clean = upname.sub(/\s*\(\d+\)\s*$/, "")
iso = CURRENCY_MAP[clean] || CURRENCY_MAP.find { |k, _| clean.include?(k) }&.last
[iso, false]
end
def parse_units(name)
match = name.match(/\((\d+)\)\s*$/)
match ? Integer(match[1]) : 1
end
end
end
end