These methods are only used while stats are being generated; once stats are generated, they aren't used anymore. So there's no need to store them using the Dalli cache. Furthermore, there are polls (and even budgets) with hundreds of thousands of participants. Calculating stats for them takes a very long time because we can't store all those records in the Dalli cache. However, since these records aren't used once the stats are generated, we can store them in an instance variable while we generate the stats, speeding up the process.
182 lines
5.7 KiB
Ruby
182 lines
5.7 KiB
Ruby
class Budget::Stats
|
|
include Statisticable
|
|
alias_method :budget, :resource
|
|
|
|
def self.stats_methods
|
|
super + support_phase_methods + vote_phase_methods
|
|
end
|
|
|
|
def self.support_phase_methods
|
|
%i[total_participants_support_phase total_budget_investments
|
|
total_selected_investments total_unfeasible_investments headings]
|
|
end
|
|
|
|
def self.vote_phase_methods
|
|
%i[total_votes total_participants_vote_phase]
|
|
end
|
|
|
|
def stats_methods
|
|
base_stats_methods + participation_methods + phase_methods
|
|
end
|
|
|
|
def phases
|
|
%w[support vote].select { |phase| send("#{phase}_phase_finished?") }
|
|
end
|
|
|
|
def all_phases
|
|
return phases unless phases.many?
|
|
|
|
[*phases, "every"]
|
|
end
|
|
|
|
def support_phase_finished?
|
|
budget.valuating_or_later?
|
|
end
|
|
|
|
def vote_phase_finished?
|
|
budget.finished?
|
|
end
|
|
|
|
def total_participants
|
|
participants.distinct.count
|
|
end
|
|
|
|
def total_participants_support_phase
|
|
voters.count
|
|
end
|
|
|
|
def total_participants_vote_phase
|
|
(balloters + poll_ballot_voters).uniq.count
|
|
end
|
|
|
|
def total_budget_investments
|
|
budget.investments.count
|
|
end
|
|
|
|
def total_votes
|
|
budget.ballots.pluck(:ballot_lines_count).inject(0) { |sum, x| sum + x }
|
|
end
|
|
|
|
def total_selected_investments
|
|
budget.investments.selected.count
|
|
end
|
|
|
|
def total_unfeasible_investments
|
|
budget.investments.unfeasible.count
|
|
end
|
|
|
|
def headings
|
|
groups = Hash.new(0)
|
|
budget.headings.order("id ASC").each do |heading|
|
|
groups[heading.id] = Hash.new(0).merge(calculate_heading_totals(heading))
|
|
end
|
|
|
|
groups[:total] = Hash.new(0)
|
|
groups[:total][:total_investments_count] = groups.collect {|_k, v| v[:total_investments_count]}.sum
|
|
groups[:total][:total_participants_support_phase] = groups.collect {|_k, v| v[:total_participants_support_phase]}.sum
|
|
groups[:total][:total_participants_vote_phase] = groups.collect {|_k, v| v[:total_participants_vote_phase]}.sum
|
|
groups[:total][:total_participants_every_phase] = groups.collect {|_k, v| v[:total_participants_every_phase]}.sum
|
|
|
|
budget.headings.each do |heading|
|
|
groups[heading.id].merge!(calculate_heading_stats_with_totals(groups[heading.id], groups[:total], heading.population))
|
|
end
|
|
|
|
groups[:total][:percentage_participants_support_phase] = groups.collect {|_k, v| v[:percentage_participants_support_phase]}.sum
|
|
groups[:total][:percentage_participants_vote_phase] = groups.collect {|_k, v| v[:percentage_participants_vote_phase]}.sum
|
|
groups[:total][:percentage_participants_every_phase] = groups.collect {|_k, v| v[:percentage_participants_every_phase]}.sum
|
|
|
|
groups
|
|
end
|
|
|
|
private
|
|
|
|
def phase_methods
|
|
phases.map { |phase| self.class.send("#{phase}_phase_methods") }.flatten
|
|
end
|
|
|
|
def participant_ids
|
|
phases.map { |phase| send("participant_ids_#{phase}_phase") }.flatten.uniq
|
|
end
|
|
|
|
def participant_ids_support_phase
|
|
(authors + voters).uniq
|
|
end
|
|
|
|
def participant_ids_vote_phase
|
|
(balloters + poll_ballot_voters).uniq
|
|
end
|
|
|
|
def authors
|
|
@authors ||= budget.investments.pluck(:author_id)
|
|
end
|
|
|
|
def voters
|
|
@voters ||= supports(budget).distinct.pluck(:voter_id)
|
|
end
|
|
|
|
def balloters
|
|
@balloters ||= budget.ballots.where("ballot_lines_count > ?", 0).distinct.pluck(:user_id).compact
|
|
end
|
|
|
|
def poll_ballot_voters
|
|
@poll_ballot_voters ||= budget.poll ? budget.poll.voters.pluck(:user_id) : []
|
|
end
|
|
|
|
def balloters_by_heading(heading_id)
|
|
stats_cache("balloters_by_heading_#{heading_id}") do
|
|
budget.ballots.joins(:lines)
|
|
.where(budget_ballot_lines: { heading_id: heading_id} )
|
|
.distinct.pluck(:user_id)
|
|
end
|
|
end
|
|
|
|
def voters_by_heading(heading)
|
|
stats_cache("voters_by_heading_#{heading.id}") do
|
|
supports(heading).distinct.pluck(:voter_id)
|
|
end
|
|
end
|
|
|
|
def calculate_heading_totals(heading)
|
|
{
|
|
total_investments_count: heading.investments.count,
|
|
total_participants_support_phase: voters_by_heading(heading).count,
|
|
total_participants_vote_phase: balloters_by_heading(heading.id).count,
|
|
total_participants_every_phase: voters_and_balloters_by_heading(heading)
|
|
}
|
|
end
|
|
|
|
def calculate_heading_stats_with_totals(heading_totals, groups_totals, population)
|
|
{
|
|
percentage_participants_support_phase: participants_percent(heading_totals, groups_totals, :total_participants_support_phase),
|
|
percentage_district_population_support_phase: population_percent(population, heading_totals[:total_participants_support_phase]),
|
|
percentage_participants_vote_phase: participants_percent(heading_totals, groups_totals, :total_participants_vote_phase),
|
|
percentage_district_population_vote_phase: population_percent(population, heading_totals[:total_participants_vote_phase]),
|
|
percentage_participants_every_phase: participants_percent(heading_totals, groups_totals, :total_participants_every_phase),
|
|
percentage_district_population_every_phase: population_percent(population, heading_totals[:total_participants_every_phase])
|
|
}
|
|
end
|
|
|
|
def voters_and_balloters_by_heading(heading)
|
|
(voters_by_heading(heading) + balloters_by_heading(heading.id)).uniq.count
|
|
end
|
|
|
|
def participants_percent(heading_totals, groups_totals, phase)
|
|
calculate_percentage(heading_totals[phase], groups_totals[phase])
|
|
end
|
|
|
|
def population_percent(population, participants)
|
|
return "N/A" unless population.to_f.positive?
|
|
calculate_percentage(participants, population)
|
|
end
|
|
|
|
def supports(supportable)
|
|
Vote.where(votable: supportable.investments)
|
|
end
|
|
|
|
stats_cache(*stats_methods)
|
|
|
|
def stats_cache(key, &block)
|
|
Rails.cache.fetch("budgets_stats/#{budget.id}/#{phases.join}/#{key}/#{version}", &block)
|
|
end
|
|
end
|