class TimeSeries::Trend include ActiveModel::Validations attr_reader :current, :previous, :favorable_direction validate :values_must_be_of_same_type, :values_must_be_of_known_type def initialize(current:, previous:, series: nil, favorable_direction: nil) @current = current @previous = previous @series = series @favorable_direction = get_favorable_direction(favorable_direction) validate! end def direction if previous.nil? || current == previous "flat" elsif current && current > previous "up" else "down" end.inquiry end def color case direction when "up" favorable_direction.down? ? red_hex : green_hex when "down" favorable_direction.down? ? green_hex : red_hex else gray_hex end end def icon if direction.flat? "minus" elsif direction.up? "arrow-up" else "arrow-down" end end def value if previous.nil? current.is_a?(Money) ? Money.new(0, current.currency) : 0 else current - previous end end def percent if previous.nil? || (previous.zero? && current.zero?) 0.0 elsif previous.zero? Float::INFINITY else change = (current_amount - previous_amount) base = previous_amount.to_f (change / base * 100).round(1).to_f end end def as_json { favorable_direction: favorable_direction, direction: direction, value: value, percent: percent }.as_json end private attr_reader :series def red_hex "#F13636" # red-500 end def green_hex "#10A861" # green-600 end def gray_hex "#737373" # gray-500 end def values_must_be_of_same_type unless current.class == previous.class || [ previous, current ].any?(&:nil?) errors.add :current, :must_be_of_the_same_type_as_previous errors.add :previous, :must_be_of_the_same_type_as_current end end def values_must_be_of_known_type unless current.is_a?(Money) || current.is_a?(Numeric) || current.nil? errors.add :current, :must_be_of_type_money_numeric_or_nil end unless previous.is_a?(Money) || previous.is_a?(Numeric) || previous.nil? errors.add :previous, :must_be_of_type_money_numeric_or_nil end end def current_amount extract_numeric current end def previous_amount extract_numeric previous end def extract_numeric(obj) if obj.is_a? Money obj.amount else obj end end def get_favorable_direction(favorable_direction) direction = favorable_direction.presence || series&.favorable_direction (direction.presence_in(TimeSeries::DIRECTIONS) || "up").inquiry end end