diff --git a/app/models/debate.rb b/app/models/debate.rb index 93ca5420f..a73a1ab6c 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -122,14 +122,11 @@ class Debate < ActiveRecord::Base end def after_commented - save # updates the hot_score because there is a before_save + save # update cache when it has a new comment end def calculate_hot_score - self.hot_score = ScoreCalculator.hot_score(created_at, - cached_votes_total, - cached_votes_up, - comments_count) + self.hot_score = ScoreCalculator.hot_score(self) end def calculate_confidence_score diff --git a/app/models/legislation/proposal.rb b/app/models/legislation/proposal.rb index 43a5cf1e8..d7c5e4807 100644 --- a/app/models/legislation/proposal.rb +++ b/app/models/legislation/proposal.rb @@ -121,14 +121,11 @@ class Legislation::Proposal < ActiveRecord::Base end def after_commented - save # updates the hot_score because there is a before_save + save # update cache when it has a new comment end def calculate_hot_score - self.hot_score = ScoreCalculator.hot_score(created_at, - total_votes, - total_votes, - comments_count) + self.hot_score = ScoreCalculator.hot_score(self) end def calculate_confidence_score diff --git a/app/models/proposal.rb b/app/models/proposal.rb index fff616626..e947f69e4 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -166,14 +166,11 @@ class Proposal < ActiveRecord::Base end def after_commented - save # updates the hot_score because there is a before_save + save # update cache when it has a new comment end def calculate_hot_score - self.hot_score = ScoreCalculator.hot_score(created_at, - total_votes, - total_votes, - comments_count) + self.hot_score = ScoreCalculator.hot_score(self) end def calculate_confidence_score diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index 8c2212dae..573af438a 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -106,7 +106,7 @@ es: orders: confidence_score: Mejor valorados created_at: Nuevos - hot_score: Más activos hoy + hot_score: Más activos most_commented: Más comentados relevance: Más relevantes recommendations: Recomendaciones @@ -346,7 +346,7 @@ es: orders: confidence_score: Más apoyadas created_at: Nuevas - hot_score: Más activas hoy + hot_score: Más activas most_commented: Más comentadas relevance: Más relevantes archival_date: Archivadas diff --git a/lib/score_calculator.rb b/lib/score_calculator.rb index 4f4e21e85..df40428f8 100644 --- a/lib/score_calculator.rb +++ b/lib/score_calculator.rb @@ -1,19 +1,19 @@ module ScoreCalculator - EPOC = Time.new(2015, 6, 15).in_time_zone - COMMENT_WEIGHT = 1.0 / 5 # 1 positive vote / x comments - TIME_UNIT = 24.hours.to_f + def self.hot_score(resource) + return 0 unless resource.created_at - def self.hot_score(date, votes_total, votes_up, comments_count) - total = (votes_total + COMMENT_WEIGHT * comments_count).to_f - ups = (votes_up + COMMENT_WEIGHT * comments_count).to_f - downs = total - ups - score = ups - downs - offset = Math.log([score.abs, 1].max, 10) * (ups / [total, 1].max) - sign = score <=> 0 - seconds = ((date || Time.current) - EPOC).to_f + period = [ + Setting['hot_score_period_in_days'].to_i, + ((Time.current - resource.created_at) / 1.day).ceil + ].min - (((offset * sign) + (seconds / TIME_UNIT)) * 10000000).round + votes_total = resource.votes_for.where("created_at >= ?", period.days.ago).count + votes_up = resource.get_upvotes.where("created_at >= ?", period.days.ago).count + votes_down = votes_total - votes_up + votes_score = votes_up - votes_down + + (votes_score.to_f / period).round end def self.confidence_score(votes_total, votes_up) diff --git a/lib/tasks/votes.rake b/lib/tasks/votes.rake index 0cc595662..357f7578b 100644 --- a/lib/tasks/votes.rake +++ b/lib/tasks/votes.rake @@ -13,4 +13,15 @@ namespace :votes do end + desc "Resets hot_score to its new value" + task reset_hot_score: :environment do + models = [Debate, Proposal, Legislation::Proposal] + + models.each do |model| + model.find_each do |resource| + resource.save + print "." + end + end + end end diff --git a/spec/models/debate_spec.rb b/spec/models/debate_spec.rb index 6f9104fa8..8b6e2b052 100644 --- a/spec/models/debate_spec.rb +++ b/spec/models/debate_spec.rb @@ -236,57 +236,91 @@ describe Debate do describe '#hot_score' do let(:now) { Time.current } - it "increases for newer debates" do - old = create(:debate, :with_hot_score, created_at: now - 1.day) - new = create(:debate, :with_hot_score, created_at: now) - expect(new.hot_score).to be > old.hot_score + it "period is correctly calculated to get exact votes per day" do + new_debate = create(:debate, created_at: 23.hours.ago) + 2.times { new_debate.vote_by(voter: create(:user), vote: "yes") } + expect(new_debate.hot_score).to be 2 + + old_debate = create(:debate, created_at: 25.hours.ago) + 2.times { old_debate.vote_by(voter: create(:user), vote: "yes") } + expect(old_debate.hot_score).to be 1 + + older_debate = create(:debate, created_at: 49.hours.ago) + 3.times { older_debate.vote_by(voter: create(:user), vote: "yes") } + expect(old_debate.hot_score).to be 1 end - it "increases for debates with more comments" do - more_comments = create(:debate, :with_hot_score, created_at: now, comments_count: 25) - less_comments = create(:debate, :with_hot_score, created_at: now, comments_count: 1) - expect(more_comments.hot_score).to be > less_comments.hot_score + it "remains the same for not voted debates" do + new = create(:debate, created_at: now) + old = create(:debate, created_at: 1.day.ago) + older = create(:debate, created_at: 2.month.ago) + expect(new.hot_score).to be 0 + expect(old.hot_score).to be 0 + expect(older.hot_score).to be 0 end it "increases for debates with more positive votes" do - more_likes = create(:debate, :with_hot_score, created_at: now, cached_votes_total: 10, cached_votes_up: 5) - less_likes = create(:debate, :with_hot_score, created_at: now, cached_votes_total: 10, cached_votes_up: 1) - expect(more_likes.hot_score).to be > less_likes.hot_score + more_positive_votes = create(:debate) + 2.times { more_positive_votes.vote_by(voter: create(:user), vote: "yes") } + + less_positive_votes = create(:debate) + less_positive_votes.vote_by(voter: create(:user), vote: "yes") + + expect(more_positive_votes.hot_score).to be > less_positive_votes.hot_score end - it "increases for debates with more confidence" do - more_confidence = create(:debate, :with_hot_score, created_at: now, cached_votes_total: 1000, cached_votes_up: 700) - less_confidence = create(:debate, :with_hot_score, created_at: now, cached_votes_total: 10, cached_votes_up: 9) - expect(more_confidence.hot_score).to be > less_confidence.hot_score + it "increases for debates with the same amount of positive votes within less days" do + newer_debate = create(:debate, created_at: now) + 5.times { newer_debate.vote_by(voter: create(:user), vote: "yes") } + + older_debate = create(:debate, created_at: 1.day.ago) + 5.times { older_debate.vote_by(voter: create(:user), vote: "yes") } + + expect(newer_debate.hot_score).to be > older_debate.hot_score end - it "decays in older debates, even if they have more votes" do - older_more_voted = create(:debate, :with_hot_score, created_at: now - 5.days, cached_votes_total: 1000, cached_votes_up: 900) - new_less_voted = create(:debate, :with_hot_score, created_at: now, cached_votes_total: 10, cached_votes_up: 9) - expect(new_less_voted.hot_score).to be > older_more_voted.hot_score + it "decreases for debates with more negative votes" do + more_negative_votes = create(:debate) + 5.times { more_negative_votes.vote_by(voter: create(:user), vote: "yes") } + 3.times { more_negative_votes.vote_by(voter: create(:user), vote: "no") } + + less_negative_votes = create(:debate) + 5.times { less_negative_votes.vote_by(voter: create(:user), vote: "yes") } + 2.times { less_negative_votes.vote_by(voter: create(:user), vote: "no") } + + expect(more_negative_votes.hot_score).to be < less_negative_votes.hot_score + end + + it "increases for debates voted within the period (last month by default)" do + newer_debate = create(:debate, created_at: 2.months.ago) + 20.times { create(:vote, votable: newer_debate, created_at: 3.days.ago) } + + older_debate = create(:debate, created_at: 2.months.ago) + 20.times { create(:vote, votable: older_debate, created_at: 40.days.ago) } + + expect(newer_debate.hot_score).to be > older_debate.hot_score end describe 'actions which affect it' do - let(:debate) { create(:debate, :with_hot_score) } - it "increases with likes" do + let(:debate) { create(:debate) } + + before do + 5.times { debate.vote_by(voter: create(:user), vote: "yes") } + 2.times { debate.vote_by(voter: create(:user), vote: "no") } + end + + it "increases with positive votes" do previous = debate.hot_score - 5.times { debate.register_vote(create(:user), true) } + 3.times { debate.vote_by(voter: create(:user), vote: "yes") } expect(previous).to be < debate.hot_score end - it "decreases with dislikes" do - debate.register_vote(create(:user), true) + it "decreases with negative votes" do previous = debate.hot_score - 3.times { debate.register_vote(create(:user), false) } + 3.times { debate.vote_by(voter: create(:user), vote: "no") } expect(previous).to be > debate.hot_score end - - it "increases with comments" do - previous = debate.hot_score - 25.times{ Comment.create(user: create(:user), commentable: debate, body: 'foobarbaz') } - expect(previous).to be < debate.reload.hot_score - end end end diff --git a/spec/models/legislation/proposal_spec.rb b/spec/models/legislation/proposal_spec.rb index 4606acabc..2e23179a4 100644 --- a/spec/models/legislation/proposal_spec.rb +++ b/spec/models/legislation/proposal_spec.rb @@ -27,4 +27,82 @@ describe Legislation::Proposal do expect(proposal).not_to be_valid end + describe '#hot_score' do + let(:now) { Time.current } + + it "period is correctly calculated to get exact votes per day" do + new_proposal = create(:legislation_proposal, created_at: 23.hours.ago) + 2.times { new_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(new_proposal.hot_score).to be 2 + + old_proposal = create(:legislation_proposal, created_at: 25.hours.ago) + 2.times { old_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(old_proposal.hot_score).to be 1 + + older_proposal = create(:legislation_proposal, created_at: 49.hours.ago) + 3.times { older_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(old_proposal.hot_score).to be 1 + end + + it "remains the same for not voted proposals" do + new = create(:legislation_proposal, created_at: now) + old = create(:legislation_proposal, created_at: 1.day.ago) + older = create(:legislation_proposal, created_at: 2.month.ago) + expect(new.hot_score).to be 0 + expect(old.hot_score).to be 0 + expect(older.hot_score).to be 0 + end + + it "increases for proposals with more positive votes" do + more_positive_votes = create(:legislation_proposal) + 2.times { more_positive_votes.vote_by(voter: create(:user), vote: "yes") } + + less_positive_votes = create(:legislation_proposal) + less_positive_votes.vote_by(voter: create(:user), vote: "yes") + + expect(more_positive_votes.hot_score).to be > less_positive_votes.hot_score + end + + it "increases for proposals with the same amount of positive votes within less days" do + newer_proposal = create(:legislation_proposal, created_at: now) + 5.times { newer_proposal.vote_by(voter: create(:user), vote: "yes") } + + older_proposal = create(:legislation_proposal, created_at: 1.day.ago) + 5.times { older_proposal.vote_by(voter: create(:user), vote: "yes") } + + expect(newer_proposal.hot_score).to be > older_proposal.hot_score + end + + it "increases for proposals voted within the period (last month by default)" do + newer_proposal = create(:legislation_proposal, created_at: 2.months.ago) + 20.times { create(:vote, votable: newer_proposal, created_at: 3.days.ago) } + + older_proposal = create(:legislation_proposal, created_at: 2.months.ago) + 20.times { create(:vote, votable: older_proposal, created_at: 40.days.ago) } + + expect(newer_proposal.hot_score).to be > older_proposal.hot_score + end + + describe 'actions which affect it' do + + let(:proposal) { create(:legislation_proposal) } + + before do + 5.times { proposal.vote_by(voter: create(:user), vote: "yes") } + 2.times { proposal.vote_by(voter: create(:user), vote: "no") } + end + + it "increases with positive votes" do + previous = proposal.hot_score + 3.times { proposal.vote_by(voter: create(:user), vote: "yes") } + expect(previous).to be < proposal.hot_score + end + + it "decreases with negative votes" do + previous = proposal.hot_score + 3.times { proposal.vote_by(voter: create(:user), vote: "no") } + expect(previous).to be > proposal.hot_score + end + end + end end diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index fa4226764..ff6cfb117 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -254,49 +254,78 @@ describe Proposal do describe '#hot_score' do let(:now) { Time.current } - it "increases for newer proposals" do - old = create(:proposal, :with_hot_score, created_at: now - 1.day) - new = create(:proposal, :with_hot_score, created_at: now) - expect(new.hot_score).to be > old.hot_score + it "period is correctly calculated to get exact votes per day" do + new_proposal = create(:proposal, created_at: 23.hours.ago) + 2.times { new_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(new_proposal.hot_score).to be 2 + + old_proposal = create(:proposal, created_at: 25.hours.ago) + 2.times { old_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(old_proposal.hot_score).to be 1 + + older_proposal = create(:proposal, created_at: 49.hours.ago) + 3.times { older_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(old_proposal.hot_score).to be 1 end - it "increases for proposals with more comments" do - more_comments = create(:proposal, :with_hot_score, created_at: now, comments_count: 25) - less_comments = create(:proposal, :with_hot_score, created_at: now, comments_count: 1) - expect(more_comments.hot_score).to be > less_comments.hot_score + it "remains the same for not voted proposals" do + new = create(:proposal, created_at: now) + old = create(:proposal, created_at: 1.day.ago) + older = create(:proposal, created_at: 2.month.ago) + expect(new.hot_score).to be 0 + expect(old.hot_score).to be 0 + expect(older.hot_score).to be 0 end it "increases for proposals with more positive votes" do - more_likes = create(:proposal, :with_hot_score, created_at: now, cached_votes_up: 5) - less_likes = create(:proposal, :with_hot_score, created_at: now, cached_votes_up: 1) - expect(more_likes.hot_score).to be > less_likes.hot_score + more_positive_votes = create(:proposal) + 2.times { more_positive_votes.vote_by(voter: create(:user), vote: "yes") } + + less_positive_votes = create(:proposal) + less_positive_votes.vote_by(voter: create(:user), vote: "yes") + + expect(more_positive_votes.hot_score).to be > less_positive_votes.hot_score end - it "increases for proposals with more confidence" do - more_confidence = create(:proposal, :with_hot_score, created_at: now, cached_votes_up: 700) - less_confidence = create(:proposal, :with_hot_score, created_at: now, cached_votes_up: 9) - expect(more_confidence.hot_score).to be > less_confidence.hot_score + it "increases for proposals with the same amount of positive votes within less days" do + newer_proposal = create(:proposal, created_at: now) + 5.times { newer_proposal.vote_by(voter: create(:user), vote: "yes") } + + older_proposal = create(:proposal, created_at: 1.day.ago) + 5.times { older_proposal.vote_by(voter: create(:user), vote: "yes") } + + expect(newer_proposal.hot_score).to be > older_proposal.hot_score end - it "decays in older proposals, even if they have more votes" do - older_more_voted = create(:proposal, :with_hot_score, created_at: now - 5.days, cached_votes_up: 900) - new_less_voted = create(:proposal, :with_hot_score, created_at: now, cached_votes_up: 9) - expect(new_less_voted.hot_score).to be > older_more_voted.hot_score + it "increases for proposals voted within the period (last month by default)" do + newer_proposal = create(:proposal, created_at: 2.months.ago) + 20.times { create(:vote, votable: newer_proposal, created_at: 3.days.ago) } + + older_proposal = create(:proposal, created_at: 2.months.ago) + 20.times { create(:vote, votable: older_proposal, created_at: 40.days.ago) } + + expect(newer_proposal.hot_score).to be > older_proposal.hot_score end describe 'actions which affect it' do - let(:proposal) { create(:proposal, :with_hot_score) } - it "increases with votes" do - previous = proposal.hot_score - 5.times { proposal.register_vote(create(:user, verified_at: Time.current), true) } - expect(previous).to be < proposal.reload.hot_score + let(:proposal) { create(:proposal) } + + before do + 5.times { proposal.vote_by(voter: create(:user), vote: "yes") } + 2.times { proposal.vote_by(voter: create(:user), vote: "no") } end - it "increases with comments" do + it "increases with positive votes" do previous = proposal.hot_score - 25.times{ Comment.create(user: create(:user), commentable: proposal, body: 'foobarbaz') } - expect(previous).to be < proposal.reload.hot_score + 3.times { proposal.vote_by(voter: create(:user), vote: "yes") } + expect(previous).to be < proposal.hot_score + end + + it "decreases with negative votes" do + previous = proposal.hot_score + 3.times { proposal.vote_by(voter: create(:user), vote: "no") } + expect(previous).to be > proposal.hot_score end end end