BathHacked/energy-sparks

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

Summary

Maintainability
A
0 mins
Test Coverage
module Charts
  # Encapsulates presenting a list of meters used to populate a select box for driving a MeterSelectionChartComponent
  #
  # Produces a list of objects that can be used to populate a select box using +options_from_collection_for_select+
  #
  # Also produces a hash of meters to date ranges that can be used to produce chart titles and subtitles that describe
  # the date ranges being shown on the chart.
  #
  # By default it will include all meters for the given fuel type that have readings. But supports options for
  # further filtering of the list of meters via the +filter+ option. Any meter for which the filter returns true will
  # be dropped from the list.
  #
  # Some charts include an option to show data for the entire school as well as individual meters. This can be configured
  # via the +include_whole_school+ option.
  #
  # The date ranges produced by +date+ranges_by_meter+ can be restricted using +date_window+
  #
  # Currently this class uses the meters returned from a MeterCollection rather than querying for meters from
  # the database.
  class MeterSelection
    attr_reader :school

    # @param School school the school whose data will be displayed
    # @param MeterCollection meter_collection the aggregate school, used to find meters and data ranges
    # @param Symbol fuel_type specifies the fuel type of the meters to be selected
    # @param filter optional, a filter to be applied to list of meters, should return true for any to be dropped
    # @param boolean include_whole_school specifies whether there should be a "Whole school" option included in list
    # This will be based on the aggregate meter for the specified +fuel_type+
    # @param Integer date_window optional, used to build date ranges for each meter for dynamically populating sub titles
    # with date ranges
    # @param String whole_school_title_key, i18n key used for the meter name for the aggregate meter, if included
    # @param String whole_school_label_key, i18n key used for the display name for the aggregate meter, if included
    #
    # i18n-tasks-use t('advice_pages.charts.the_whole_school')
    # i18n-tasks-use t('advice_pages.charts.whole_school')
    def initialize(school,
                   meter_collection,
                   fuel_type,
                   filter: nil,
                   include_whole_school: true,
                   date_window: nil,
                   whole_school_title_key: 'advice_pages.charts.the_whole_school',
                   whole_school_label_key: 'advice_pages.charts.whole_school')
      @school = school
      @meter_collection = meter_collection
      @fuel_type = fuel_type
      @include_whole_school = include_whole_school
      @date_window = date_window
      @filter = filter
      @whole_school_title_key = whole_school_title_key
      @whole_school_label_key = whole_school_label_key
    end

    def meter_selection_options
      @include_whole_school ? displayable_meters.prepend(aggregate_meter_adapter) : displayable_meters
    end

    def date_ranges_by_meter
      ranges_by_meter = {}
      if @include_whole_school
        ranges_by_meter[aggregate_meter.mpan_mprn] = {
          meter: aggregate_meter_adapter,
          start_date: start_date(aggregate_meter),
          end_date: aggregate_meter.amr_data.end_date
        }
      end
      # if single meter, then the underlying meters is the aggregate meter
      # just return the range for aggregate adapter in this case so its labelled
      # correctly as "the whole school"
      return ranges_by_meter if @include_whole_school && underlying_meters.count == 1
      displayable_meters.each do |analytics_meter|
        end_date = analytics_meter.amr_data.end_date
        start_date = start_date(analytics_meter)
        ranges_by_meter[analytics_meter.mpan_mprn] = {
          meter: analytics_meter,
          start_date: start_date,
          end_date: end_date
        }
      end
      ranges_by_meter
    end

    def underlying_meters
      @underlying_meters ||= displayable_meters
    end

    private

    def aggregate_meter
      @meter_collection.aggregate_meter(@fuel_type)
    end

    def displayable_meters
      meters = case @fuel_type
               when :electricity
                 @meter_collection.electricity_meters
               when :gas
                 @meter_collection.heat_meters
               when :storage_heater, :storage_heaters
                 @meter_collection.storage_heater_meters # TODO likely not used
               else
                 raise 'Unexpected fuel type'
               end
      meters = meters.keep_if { |m| m.amr_data.any? } # only show meters with readings
      meters = meters.reject(&@filter) if @filter # apply optional filter
      meters.sort_by(&:mpan_mprn)
    end

    def start_date(meter)
      earliest_date = meter.amr_data.start_date
      return earliest_date unless @date_window.present?

      desired_date = meter.amr_data.end_date - @date_window
      [desired_date, earliest_date].max
    end

    # Used to override default labelling methods for aggregate meter
    def aggregate_meter_adapter
      adapter = ActiveSupport::OrderedOptions.new
      adapter.name_or_mpan_mprn = I18n.t(@whole_school_title_key)
      adapter.mpan_mprn = aggregate_meter.mpan_mprn
      adapter.display_name = I18n.t(@whole_school_label_key)
      adapter
    end
  end
end