Files
grecia/spec/models/poll/stats_spec.rb
Javi Martín 1d85a63e7c Calculate age stats based on the participation date
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).
2024-05-13 15:42:37 +02:00

331 lines
11 KiB
Ruby

require "rails_helper"
describe Poll::Stats do
let(:poll) { create(:poll) }
let(:stats) { Poll::Stats.new(poll) }
describe "#participants" do
it "includes hidden users" do
create(:poll_voter, poll: poll)
create(:poll_voter, poll: poll, user: create(:user, :level_two, :hidden))
expect(stats.participants.count).to eq(2)
end
end
describe "total participants" do
before { allow(stats).to receive(:total_web_white).and_return(1) }
it "supports every channel" do
3.times { create(:poll_voter, :from_web, poll: poll) }
create(:poll_recount, :from_booth, poll: poll,
total_amount: 8,
white_amount: 4,
null_amount: 1)
expect(stats.total_participants_web).to eq(3)
expect(stats.total_participants_booth).to eq(13)
expect(stats.total_participants).to eq(16)
end
end
describe "#total_participants_booth" do
it "uses recounts even if there are discrepancies when recounting" do
create(:poll_recount, :from_booth, poll: poll, total_amount: 1)
2.times { create(:poll_voter, :from_booth, poll: poll) }
expect(stats.total_participants_booth).to eq(1)
end
end
describe "total participants percentage by channel" do
it "is relative to the total amount of participants" do
create(:poll_voter, :from_web, poll: poll)
create(:poll_recount, :from_booth, poll: poll, total_amount: 5)
expect(stats.total_participants_web_percentage).to eq(16.667)
expect(stats.total_participants_booth_percentage).to eq(83.333)
end
end
describe "#total_web_valid" do
before { allow(stats).to receive(:total_web_white).and_return(1) }
it "returns only valid votes" do
3.times { create(:poll_voter, :from_web, poll: poll) }
expect(stats.total_web_valid).to eq(2)
end
end
describe "#total_web_null" do
it "returns 0" do
expect(stats.total_web_null).to eq(0)
end
end
describe "#total_booth_valid" do
it "sums the total amounts in the recounts" do
create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 1)
create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 2)
expect(stats.total_booth_valid).to eq(7)
end
end
describe "#total_booth_white" do
it "sums the white amounts in the recounts" do
create(:poll_recount, :from_booth, poll: poll, white_amount: 120, total_amount: 3)
create(:poll_recount, :from_booth, poll: poll, white_amount: 203, null_amount: 5)
expect(stats.total_booth_white).to eq(323)
end
end
describe "#total_booth_null" do
it "sums the null amounts in the recounts" do
create(:poll_recount, :from_booth, poll: poll, null_amount: 125, total_amount: 3)
create(:poll_recount, :from_booth, poll: poll, null_amount: 34, white_amount: 5)
expect(stats.total_booth_null).to eq(159)
end
end
describe "valid percentage by channel" do
it "is relative to the total amount of valid votes" do
create(:poll_recount, :from_booth, poll: poll, total_amount: 2)
create(:poll_voter, :from_web, poll: poll)
expect(stats.valid_percentage_web).to eq(33.333)
expect(stats.valid_percentage_booth).to eq(66.667)
end
end
describe "white percentage by channel" do
before { allow(stats).to receive(:total_web_white).and_return(10) }
it "is relative to the total amount of white votes" do
create(:poll_recount, :from_booth, poll: poll, white_amount: 70)
expect(stats.white_percentage_web).to eq(12.5)
expect(stats.white_percentage_booth).to eq(87.5)
end
end
describe "null percentage by channel" do
it "only accepts null votes from booth" do
create(:poll_recount, :from_booth, poll: poll, null_amount: 70)
expect(stats.null_percentage_web).to eq(0)
expect(stats.null_percentage_booth).to eq(100)
end
end
describe "#total_valid_votes" do
it "counts valid votes from every channel" do
2.times { create(:poll_voter, :from_web, poll: poll) }
create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 10)
create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 20)
expect(stats.total_valid_votes).to eq(9)
end
end
describe "#total_white_votes" do
before { allow(stats).to receive(:total_web_white).and_return(9) }
it "counts white votes on every channel" do
create(:poll_recount, :from_booth, poll: poll, white_amount: 12)
expect(stats.total_white_votes).to eq(21)
end
end
describe "#total_null_votes" do
it "only accepts null votes from booth" do
create(:poll_recount, :from_booth, poll: poll, null_amount: 32)
expect(stats.total_null_votes).to eq(32)
end
end
describe "total percentage by type" do
before { allow(stats).to receive(:total_web_white).and_return(1) }
it "is relative to the total amount of votes" do
3.times { create(:poll_voter, :from_web, poll: poll) }
create(:poll_recount, :from_booth, poll: poll,
total_amount: 8,
white_amount: 5,
null_amount: 4)
expect(stats.total_valid_percentage).to eq(50)
expect(stats.total_white_percentage).to eq(30)
expect(stats.total_null_percentage).to eq(20)
end
end
describe "#participants_by_age" do
it "returns stats based on what happened when the voting took place" do
travel_to(100.years.ago) do
[16, 18, 32, 32, 33, 34, 64, 65, 71, 73, 90, 99, 105].each do |age|
create(:user, date_of_birth: age.years.ago)
end
create(:poll, starts_at: 1.minute.from_now, ends_at: 2.minutes.from_now)
end
stats = Poll::Stats.new(Poll.last)
allow(stats).to receive(:participants).and_return(User.all)
expect(stats.participants_by_age["16 - 19"][:count]).to eq 2
expect(stats.participants_by_age["20 - 24"][:count]).to eq 0
expect(stats.participants_by_age["25 - 29"][:count]).to eq 0
expect(stats.participants_by_age["30 - 34"][:count]).to eq 4
expect(stats.participants_by_age["35 - 39"][:count]).to eq 0
expect(stats.participants_by_age["40 - 44"][:count]).to eq 0
expect(stats.participants_by_age["45 - 49"][:count]).to eq 0
expect(stats.participants_by_age["50 - 54"][:count]).to eq 0
expect(stats.participants_by_age["55 - 59"][:count]).to eq 0
expect(stats.participants_by_age["60 - 64"][:count]).to eq 1
expect(stats.participants_by_age["65 - 69"][:count]).to eq 1
expect(stats.participants_by_age["70 - 74"][:count]).to eq 2
expect(stats.participants_by_age["75 - 79"][:count]).to eq 0
expect(stats.participants_by_age["80 - 84"][:count]).to eq 0
expect(stats.participants_by_age["85 - 89"][:count]).to eq 0
expect(stats.participants_by_age["90 - 300"][:count]).to eq 3
end
end
describe "#participation_date", :with_frozen_time do
let(:poll) { create(:poll, starts_at: 3.years.ago, ends_at: 2.years.ago) }
it "returns the date when the poll finishes" do
expect(stats.participation_date).to eq 2.years.ago
end
end
describe "#participants_by_geozone" do
it "groups by geozones in alphabetic order" do
%w[Oceania Eurasia Eastasia].each { |name| create(:geozone, name: name) }
expect(stats.participants_by_geozone.keys).to eq %w[Eastasia Eurasia Oceania]
end
it "calculates percentage relative to total participants" do
hobbiton = create(:geozone, name: "Hobbiton")
rivendel = create(:geozone, name: "Rivendel")
3.times { create(:poll_voter, poll: poll, user: create(:user, :level_two, geozone: hobbiton)) }
2.times { create(:poll_voter, poll: poll, user: create(:user, :level_two, geozone: rivendel)) }
expect(stats.participants_by_geozone["Hobbiton"][:count]).to eq 3
expect(stats.participants_by_geozone["Hobbiton"][:percentage]).to eq 60.0
expect(stats.participants_by_geozone["Rivendel"][:count]).to eq 2
expect(stats.participants_by_geozone["Rivendel"][:percentage]).to eq 40.0
end
end
describe "#total_no_demographic_data" do
before do
create(:poll_voter, :from_web, poll: poll, user: create(:user, :level_two, gender: nil))
end
context "more registered participants than participants in recounts" do
before do
create(:poll_recount, :from_booth, poll: poll, total_amount: 1)
2.times { create(:poll_voter, :from_booth, poll: poll) }
end
it "returns registered users with no demographic data" do
expect(stats.total_no_demographic_data).to eq 1
end
end
context "more participants in recounts than registered participants" do
before do
create(:poll_recount, :from_booth, poll: poll, total_amount: 3)
2.times { create(:poll_voter, :from_booth, poll: poll) }
end
it "returns registered users with no demographic data plus users not registered" do
expect(stats.total_no_demographic_data).to eq 2
end
end
end
describe "#channels" do
context "no participants" do
it "returns no channels" do
expect(stats.channels).to eq []
end
end
context "only participants from web" do
before { create(:poll_voter, :from_web, poll: poll) }
it "returns the web channel" do
expect(stats.channels).to eq ["web"]
end
end
context "only participants from booth" do
before do
create(:poll_recount, :from_booth, poll: poll, total_amount: 1)
end
it "returns the booth channel" do
expect(stats.channels).to eq ["booth"]
end
end
context "only participants from letter" do
before { create(:poll_voter, origin: "letter", poll: poll) }
it "returns the web channel" do
expect(stats.channels).to eq ["letter"]
end
end
context "participants from all channels" do
before do
create(:poll_voter, :from_web, poll: poll)
create(:poll_recount, :from_booth, poll: poll, total_amount: 1)
create(:poll_voter, origin: "letter", poll: poll)
end
it "returns all channels" do
expect(stats.channels).to eq %w[web booth letter]
end
end
end
describe "#version", :with_frozen_time do
context "record with no stats" do
it "returns a string based on the current time" do
expect(stats.version).to eq "v#{Time.current.to_i}"
end
it "doesn't overwrite the timestamp when called multiple times" do
time = Time.current
expect(stats.version).to eq "v#{time.to_i}"
unfreeze_time
travel_to 2.seconds.from_now do
expect(stats.version).to eq "v#{time.to_i}"
end
end
end
context "record with stats" do
before { poll.create_stats_version(updated_at: 1.day.ago) }
it "returns the version of the existing stats" do
expect(stats.version).to eq "v#{1.day.ago.to_i}"
end
end
end
end