mysociety/alaveteli

View on GitHub
app/models/statistics.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# Collection of classes and methods to generate various statistics
# Many of the top-level methods ought to be extracted to classes.
module Statistics
  def self.public_bodies
    per_graph = 10
    minimum_requests = AlaveteliConfiguration.minimum_requests_for_statistics
    # Make sure minimum_requests is > 0 to avoid division-by-zero
    minimum_requests = [minimum_requests, 1].max
    total_column = 'info_requests_count'

    graph_list = []

    [
      [total_column,
       [{ title: _('Public bodies with the most requests'),
          y_axis: _('Number of requests'),
          highest: true }]],
      ['info_requests_successful_count',
       [{ title: _('Public bodies with the most successful requests'),
          y_axis: _('Percentage of total requests'),
          highest: true },
        { title: _('Public bodies with the fewest successful requests'),
          y_axis: _('Percentage of total requests'),
          highest: false }]],
      ['info_requests_overdue_count',
       [{ title: _('Public bodies with most overdue requests'),
          y_axis: _('Percentage of requests that are overdue'),
          highest: true }]],
      ['info_requests_not_held_count',
       [{ title: _('Public bodies that most frequently replied with ' \
                   '"Not Held"'),
          y_axis: _('Percentage of total requests'),
          highest: true }]]
    ].each do |column, graphs_properties|
      graphs_properties.each do |graph_properties|
        percentages = (column != total_column)
        highest = graph_properties[:highest]

        data = nil
        data =
          if percentages
            PublicBody.get_request_percentages(column,
                                               per_graph,
                                               highest,
                                               minimum_requests)
          else
            PublicBody.get_request_totals(per_graph,
                                          highest,
                                          minimum_requests)
          end

        if data
          graph_list.push simplify_stats_for_graphs(data,
                                                    column,
                                                    percentages,
                                                    graph_properties)
        end
      end
    end

    graph_list
  end

  # This is a helper method to take data returned by the above method and
  # converting them to simpler data structure that can be rendered by a
  # Javascript graph library.
  def self.simplify_stats_for_graphs(data, column, percentages, graph_properties)
    # Copy the data, only taking known-to-be-safe keys:
    result = Hash.new { |h, k| h[k] = [] }

    result.update Hash[data.select do |key, _value|
      %w[y_values
         y_max
         totals
         cis_below
         cis_above].include?(key)
    end]

    # Extract data about the public bodies for the x-axis,
    # tooltips, and so on:
    data['public_bodies'].each_with_index do |pb, i|
      result['x_values'] << i
      result['x_ticks'] << [i, pb.short_or_long_name.truncate(30)]
      result['tooltips'] << "#{pb.name} (#{result['totals'][i]})"
      result['public_bodies'] << {
        'name' => pb.name,
        # FIXME: This seems nasty, can we simplify it?
        'url' => Rails.application.routes.url_helpers.
                  show_public_body_path(url_name: pb.url_name)
      }
    end

    # Set graph metadata properties, like the title, axis labels, etc.
    graph_id = "#{column}-"
    graph_id += graph_properties[:highest] ? 'highest' : 'lowest'
    result.update({ 'id' => graph_id,
                    'x_axis' => _('Public Bodies'),
                    'y_axis' => graph_properties[:y_axis],
                    'errorbars' => percentages,
                    'title' => graph_properties[:title] })
  end

  def self.leaderboard
    leaderboard = Leaderboard.new
    { all_time_requesters: leaderboard.all_time_requesters,
      last_28_day_requesters: leaderboard.last_28_day_requesters,
      all_time_commenters: leaderboard.all_time_commenters,
      last_28_day_commenters: leaderboard.last_28_day_commenters }
  end

  def self.user_json_for_api(user_statistics)
    user_statistics.each do |k, v|
      user_statistics[k] = v.map { |u, c| { user: u.json_for_api, count: c } }
    end
  end

  def self.by_week_to_today_with_noughts(counts_by_week, start_date)
    earliest_week = start_date.to_date.at_beginning_of_week
    latest_week = Date.current.at_beginning_of_week

    counts_by_week.map! { |date, count| [date.to_s, count] }

    (earliest_week..latest_week).step(7) do |date|
      unless counts_by_week.any? { |c| c.first == date.to_s }
        counts_by_week << [date.to_s, 0]
      end
    end

    counts_by_week.sort
  end
end