From a86f5847919bc2ff1f8a8391e9ff4e855cbcad9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Tue, 4 Jun 2024 19:10:35 +0200 Subject: [PATCH] Group people by age instead of birth year in stats When calculating the stats on, say, January 1st 2024, and using a group age of, say, between 20 and 24 years, we were considering that everyone born between 2000 and 2004 had between 20 and 24 years. This wasn't accurate, since most people born in 2004 would have 19 years at that point, and most people born in 1999 would have 24 years. --- app/models/user.rb | 5 ++- spec/models/budget/stats_spec.rb | 4 +- spec/models/poll/stats_spec.rb | 2 +- spec/models/user_spec.rb | 74 ++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 5421b56ce..d13c27b2f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -120,7 +120,10 @@ class User < ApplicationRecord where("username ILIKE ? OR email ILIKE ? OR document_number ILIKE ?", search, search, search) end scope :between_ages, ->(from, to, at_time: Time.current) do - where(date_of_birth: (at_time - to.years).beginning_of_year..(at_time - from.years).end_of_year) + start_date = at_time - (to + 1).years + 1.day + end_date = at_time - from.years + + where(date_of_birth: start_date.beginning_of_day..end_date.end_of_day) end before_validation :clean_document_number diff --git a/spec/models/budget/stats_spec.rb b/spec/models/budget/stats_spec.rb index d076d9c0b..54a133428 100644 --- a/spec/models/budget/stats_spec.rb +++ b/spec/models/budget/stats_spec.rb @@ -159,7 +159,7 @@ describe Budget::Stats do describe "#participants_by_age" do it "returns the age groups hash" do [21, 22, 23, 23, 34, 42, 43, 44, 50, 51].each do |age| - create(:user, date_of_birth: budget.phases.balloting.ends_at - age.years) + create(:user, date_of_birth: budget.phases.balloting.ends_at - age.years - rand(0..11).months) end allow(stats).to receive(:participants).and_return(User.all) @@ -182,7 +182,7 @@ describe Budget::Stats do budget = travel_to(50.years.ago) { create(:budget, :finished) } [21, 22, 23, 23, 34, 42, 43, 44, 50, 51].each do |age| - create(:user, date_of_birth: budget.phases.balloting.ends_at - age.years) + create(:user, date_of_birth: budget.phases.balloting.ends_at - age.years - rand(0..11).months) end stats = Budget::Stats.new(budget) diff --git a/spec/models/poll/stats_spec.rb b/spec/models/poll/stats_spec.rb index 9bf5f56c2..7b962a2c0 100644 --- a/spec/models/poll/stats_spec.rb +++ b/spec/models/poll/stats_spec.rb @@ -169,7 +169,7 @@ describe Poll::Stats 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) + create(:user, date_of_birth: age.years.ago - rand(0..11).months) end create(:poll, starts_at: 1.minute.from_now, ends_at: 2.minutes.from_now) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f3e59ffd6..62a0663be 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -438,6 +438,80 @@ describe User do expect(User.between_ages(51, 100).count).to eq 1 end + it "does not consider that people born in the same year share the same age" do + april_1st_user = create(:user, date_of_birth: Time.zone.local(2001, 4, 1)) + april_2nd_user = create(:user, date_of_birth: Time.zone.local(2001, 4, 2)) + april_3rd_user = create(:user, date_of_birth: Time.zone.local(2001, 4, 3)) + + travel_to "2021-04-02" do + expect(User.between_ages(18, 19)).to eq [april_3rd_user] + expect(User.between_ages(20, 21)).to match_array [april_1st_user, april_2nd_user] + expect(User.between_ages(19, 20)).to match_array [april_1st_user, april_2nd_user, april_3rd_user] + end + end + + it "works on leap years with users born on leap years" do + leap_day_user = create(:user, date_of_birth: Time.zone.local(2000, 2, 29)) + march_1st_user = create(:user, date_of_birth: Time.zone.local(2000, 3, 1)) + + travel_to "2024-02-28" do + expect(User.between_ages(22, 23)).to match_array [leap_day_user, march_1st_user] + expect(User.between_ages(23, 24)).to match_array [leap_day_user, march_1st_user] + expect(User.between_ages(24, 25)).to eq [] + end + + travel_to "2024-02-29" do + expect(User.between_ages(22, 23)).to eq [march_1st_user] + expect(User.between_ages(23, 24)).to eq [leap_day_user, march_1st_user] + expect(User.between_ages(24, 25)).to eq [leap_day_user] + end + + travel_to "2024-03-01" do + expect(User.between_ages(22, 23)).to eq [] + expect(User.between_ages(23, 24)).to match_array [leap_day_user, march_1st_user] + expect(User.between_ages(24, 25)).to match_array [leap_day_user, march_1st_user] + end + end + + it "works on non-leap years with users born on leap years" do + leap_day_user = create(:user, date_of_birth: Time.zone.local(2000, 2, 29)) + march_1st_user = create(:user, date_of_birth: Time.zone.local(2000, 3, 1)) + + travel_to "2023-02-28" do + expect(User.between_ages(21, 22)).to match_array [leap_day_user, march_1st_user] + expect(User.between_ages(22, 23)).to match_array [leap_day_user, march_1st_user] + expect(User.between_ages(23, 24)).to eq [] + end + + travel_to "2023-03-01" do + expect(User.between_ages(21, 22)).to eq [] + expect(User.between_ages(22, 23)).to match_array [leap_day_user, march_1st_user] + expect(User.between_ages(23, 24)).to match_array [leap_day_user, march_1st_user] + end + end + + it "works on leap years with users born on non-leap years" do + march_1st_user = create(:user, date_of_birth: Time.zone.local(2001, 3, 1)) + + travel_to "2024-02-28" do + expect(User.between_ages(21, 22)).to eq [march_1st_user] + expect(User.between_ages(22, 23)).to eq [march_1st_user] + expect(User.between_ages(23, 24)).to eq [] + end + + travel_to "2024-02-29" do + expect(User.between_ages(21, 22)).to eq [march_1st_user] + expect(User.between_ages(22, 23)).to eq [march_1st_user] + expect(User.between_ages(23, 24)).to eq [] + end + + travel_to "2024-03-01" do + expect(User.between_ages(21, 22)).to eq [] + expect(User.between_ages(22, 23)).to match_array [march_1st_user] + expect(User.between_ages(23, 24)).to match_array [march_1st_user] + end + end + it "returns users between certain ages on a reference date" do reference_date = 20.years.ago