BathHacked/energy-sparks

View on GitHub
app/services/charts/annotate.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
module Charts
  class Annotate
    def initialize(school:, fuel_types: FuelTypeable::VALID_FUEL_TYPES)
      @school = school
      @fuel_types = fuel_types
    end

    def annotate_weekly(x_axis_categories)
      return if x_axis_categories.nil? || x_axis_categories.empty?

      date_categories = date_categories_for(x_axis_categories)

      weekly_relevant_observations_for(x_axis_categories, date_categories)
    end

    def annotate_daily(x_axis_start, x_axis_end)
      return if x_axis_start.blank? || x_axis_end.blank?

      daily_relevant_observations_for(x_axis_start, x_axis_end)
    end

    private

    def date_categories_for(x_axis_categories)
      x_axis_categories.map { |x_axis_category| date_for(x_axis_category) }
    end

    def relevant_observations_for(start_date, end_date)
      Observation.find_by_sql(observation_query_for(start_date, end_date))
    end

    def observation_query_for(start_date, end_date)
      <<~SQL.squish
        SELECT observations.*
        FROM   observations
               INNER JOIN intervention_types
                       ON intervention_types.id = observations.intervention_type_id
        WHERE  observation_type = #{Observation.observation_types['intervention']}
               AND intervention_types.show_on_charts = true
               AND intervention_types.fuel_type && '{#{@fuel_types.join(',')}}'
               AND ( observations.at BETWEEN '#{start_date}' AND '#{end_date}' )
               AND observations.school_id = #{@school.id}
        UNION ALL
        SELECT observations.*
        FROM   observations
               INNER JOIN activities
                       ON activities.id = observations.activity_id
               INNER JOIN activity_types
                       ON activity_types.id = activities.activity_type_id
        WHERE  observation_type = #{Observation.observation_types['activity']}
               AND activity_types.show_on_charts = true
               AND activity_types.fuel_type && '{#{@fuel_types.join(',')}}'
               AND ( observations.at BETWEEN '#{start_date}' AND '#{end_date}' )
               AND observations.school_id = #{@school.id}
      SQL
    end

    def weekly_relevant_observations_for(x_axis_categories, date_categories)
      relevant_observations = relevant_observations_for(date_categories.min, date_categories.max + 6.days)

      relevant_observations.map do |observation|
        x_axis_category = weekly_x_axis_category_for(x_axis_categories, date_categories, observation)

        annotation_for(observation: observation, x_axis_category: x_axis_category)
      end
    end

    def weekly_x_axis_category_for(x_axis_categories, date_categories, observation)
      weekly_relevant_start_date = date_categories.find { |date| (date..(date + 6.days)).cover?(observation.at.to_date) }

      x_axis_categories[date_categories.index(weekly_relevant_start_date)]
    end

    def daily_relevant_observations_for(x_axis_start, x_axis_end)
      relevant_observations = relevant_observations_for(Date.parse(x_axis_start), Date.parse(x_axis_end))

      relevant_observations.map do |observation|
        annotation_for(observation: observation, x_axis_category: observation.at.strftime('%d-%m-%Y'))
      end
    end

    def annotation_for(observation:, x_axis_category:)
      {
        id: observation.id,
        event: event_for(observation),
        date: observation.at.to_date,
        x_axis_category: x_axis_category,
        icon: icon_for(observation),
        icon_color: icon_color_for(observation),
        observation_type: observation.observation_type,
        url: url_for(observation)
      }
    end

    def url_for(observation)
      case observation.observation_type
      when 'activity' then Rails.application.routes.url_helpers.school_activity_path(@school, observation.activity)
      when 'intervention' then Rails.application.routes.url_helpers.school_intervention_path(@school, observation)
      end
    end

    def icon_for(observation)
      case observation.observation_type
      when 'activity' then observation.activity.activity_category.icon
      when 'intervention' then observation.intervention_type.intervention_type_group.icon
      end
    end

    def icon_color_for(observation)
      case observation.observation_type
      when 'activity' then '#FFFFFF'
      when 'intervention' then '#FFFFFF'
      end
    end

    def event_for(observation)
      case observation.observation_type
      when 'activity' then observation.activity.activity_category.name
      when 'intervention' then observation.intervention_type.name
      end
    end

    def abbr_month_name_lookup
      @abbr_month_name_lookup ||= I18n.t('date.abbr_month_names').map.with_index do |abbr_month_name, index|
        [abbr_month_name, I18n.t('date.abbr_month_names', locale: 'en')[index]]
      end.to_h
    end

    def date_for(x_axis_category)
      return Date.parse(x_axis_category) if I18n.locale.to_s == 'en'

      # Date.parse doesn't work with localised date strings as passed in x_axis_categories (e.g. '01 Chwe 2022')
      # so we need to "de-localise" to the default locale ('en') first (e.g. '01 Feb 2022').
      delocalised_date = x_axis_category.gsub(/\w+/) { |date_string| abbr_month_name_lookup.fetch(date_string, date_string) }
      Date.parse(delocalised_date)
    end
  end
end