frankfurter/lib/base_conversion.rb

62 lines
1.5 KiB
Ruby
Raw Permalink Normal View History

2026-05-21 01:38:14 -06:00
# frozen_string_literal: true
class BaseConversion
attr_reader :rates, :base
def initialize(rates, base:)
@rates = rates
@base = base
end
def convert
rates.group_by { |r| r[:provider] }.flat_map do |provider, group|
converted = convert_group(group).map { |r| r.merge(provider:) }
seen = {}
converted.each do |r|
key = [r[:date], r[:quote]]
raise "ambiguous bridge: #{provider} produced #{key.inspect} twice" if seen[key]
seen[key] = true
end
converted
end
end
private
def convert_group(group)
group.filter_map do |rate|
if rate[:base] == base
rate.except(:provider)
elsif rate[:quote] == base
invert(rate)
else
cross_convert(rate, group)
end
end
end
def invert(rate)
{ date: rate[:date], base:, quote: rate[:base], rate: 1.0 / rate[:rate] }
end
def cross_convert(rate, group)
bridge = group.find { |r| r[:base] == base && r[:quote] == rate[:quote] }
if bridge
return { date: rate[:date], base:, quote: rate[:base], rate: bridge[:rate] / rate[:rate] }
end
bridge = group.find { |r| r[:base] == base && r[:quote] == rate[:base] }
if bridge
return { date: rate[:date], base:, quote: rate[:quote], rate: rate[:rate] * bridge[:rate] }
end
bridge = group.find { |r| r[:base] == rate[:base] && r[:quote] == base }
if bridge
return { date: rate[:date], base:, quote: rate[:quote], rate: rate[:rate] / bridge[:rate] }
end
nil
end
end