We were calculating the age stats based on the age of the users who participated... at the moment where we were calculating the stats. That means that, if 20 years ago, 1000 people who were 16 years old participated, they would be shown as having 36 years in the stats. Instead, we want to show the stats at the time when the process took place, so we're implementing a `participation_date` method. Note that, for polls, we could actually use the `age` column in the `poll_voters` table. However, doing so would be harder, would only work for polls but not for budgets, and it wouldn't be statistically very relevant, since the stats are shown by age groups, and only a small percentage of people would change their age group (and only to the nearest one) between the time they participate and the time the process ends. We might use the `poll_voters` table in the future, though, since we have a similar issue with geozones and genders, and using the information in `poll_voters` would solve it as well (only for polls, though). Also note that we're using the `ends_at` dates because some people but be too young to vote when a process starts but old enough to vote when the process ends. Finally, note that we might need to change the way we calculate the participation date for a budget, since some budgets might not enabled every phase. Not sure how stats work in that scenario (even before these changes).
199 lines
5.9 KiB
Ruby
199 lines
5.9 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 participation_date
|
|
send("#{phases.last}_phase_participation_date")
|
|
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_supports
|
|
supports(budget).count
|
|
end
|
|
|
|
def total_votes
|
|
budget.ballots.pluck(:ballot_lines_count).sum
|
|
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.sum { |_k, v| v[:total_investments_count] }
|
|
groups[:total][:total_participants_support_phase] = groups.sum { |_k, v| v[:total_participants_support_phase] }
|
|
groups[:total][:total_participants_vote_phase] = groups.sum { |_k, v| v[:total_participants_vote_phase] }
|
|
groups[:total][:total_participants_every_phase] = groups.sum { |_k, v| v[:total_participants_every_phase] }
|
|
|
|
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.sum { |_k, v| v[:percentage_participants_support_phase] }
|
|
groups[:total][:percentage_participants_vote_phase] = groups.sum { |_k, v| v[:percentage_participants_vote_phase] }
|
|
groups[:total][:percentage_participants_every_phase] = groups.sum { |_k, v| v[:percentage_participants_every_phase] }
|
|
|
|
groups
|
|
end
|
|
|
|
private
|
|
|
|
def phase_methods
|
|
phases.map { |phase| self.class.send("#{phase}_phase_methods") }.flatten
|
|
end
|
|
|
|
def support_phase_participation_date
|
|
budget.phases.selecting.ends_at
|
|
end
|
|
|
|
def vote_phase_participation_date
|
|
budget.phases.balloting.ends_at
|
|
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, &)
|
|
Rails.cache.fetch("budgets_stats/#{budget.id}/#{phases.join}/#{key}/#{version}", &)
|
|
end
|
|
end
|