Merge branch 'proposals' of github.com:AyuntamientoMadrid/participacion into proposals

This commit is contained in:
rgarcia
2015-09-14 21:25:20 +02:00
14 changed files with 235 additions and 33 deletions

View File

@@ -102,28 +102,15 @@ class Debate < ActiveRecord::Base
end
def calculate_hot_score
z = 1.96 # Normal distribution with a confidence of 0.95
time_unit = 1.0 * 12.hours
start = Time.new(2015, 6, 15)
comments_weight = 1.0/20 # 1 positive vote / x comments
weighted_score = 0
n = cached_votes_total + comments_weight * comments_count
if n > 0 then
pos = cached_votes_up + comments_weight * comments_count
phat = 1.0 * pos / n
weighted_score = (phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
end
age_in_units = 1.0 * ((created_at || Time.now) - start) / time_unit
self.hot_score = ((age_in_units**2 + weighted_score)*1000).round
self.hot_score = ScoreCalculator.hot_score(created_at,
cached_votes_total,
cached_votes_up,
comments_count)
end
def calculate_confidence_score
return unless cached_votes_total > 0
self.confidence_score = cached_votes_score * (cached_votes_up / cached_votes_total.to_f) * 100
self.confidence_score = ScoreCalculator.confidence_score(cached_votes_total,
cached_votes_up)
end
def self.search(terms)

View File

@@ -27,6 +27,8 @@ class Proposal < ActiveRecord::Base
before_validation :sanitize_tag_list
before_validation :set_responsible_name
before_save :calculate_hot_score, :calculate_confidence_score
scope :for_render, -> { includes(:tags) }
scope :sort_by_hot_score , -> { order(hot_score: :desc) }
scope :sort_by_confidence_score , -> { order(confidence_score: :desc) }
@@ -87,6 +89,22 @@ class Proposal < ActiveRecord::Base
"#{Setting.value_for("proposal_code_prefix")}-#{created_at.strftime('%Y-%M')}-#{id}"
end
def after_commented
save # updates the hot_score because there is a before_save
end
def calculate_hot_score
self.hot_score = ScoreCalculator.hot_score(created_at,
cached_votes_up,
cached_votes_up,
comments_count)
end
def calculate_confidence_score
self.confidence_score = ScoreCalculator.confidence_score(cached_votes_up,
cached_votes_up)
end
def self.title_max_length
@@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80
end
@@ -126,6 +144,7 @@ class Proposal < ActiveRecord::Base
self.responsible_name = author.document_number
end
end
private
def validate_description_length

View File

@@ -27,6 +27,13 @@
<%= f.cktext_area :description, maxlength: Proposal.description_max_length, ckeditor: { language: I18n.locale }, label: false %>
</div>
<!-- link for external video URL -->
<!-- <div class="small-12 column">
<%#= f.label :external_url, t("proposals.form.proposal_external_video_url") %>
<span class="note"><%#= t("proposals.form.proposal_external_video_url_note") %></span>
<%#= f.text_field :external_url, placeholder: t("proposals.form.proposal_external_video_url"), label: false %>
</div> -->
<!-- /. link for external video URL -->
<div class="small-12 column">
<%= f.label :external_url, t("proposals.form.proposal_external_url") %>
<%= f.text_field :external_url, placeholder: t("proposals.form.proposal_external_url"), label: false %>

View File

@@ -16,6 +16,12 @@
</div>
</div>
<div class="small-12 medium-3 column">
<aside class="sidebar" role="complementary">
<%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button radius expand' %>
<%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button radius expand' %>
</aside>
</div>
</div>
</section>
<% end %>

View File

@@ -178,6 +178,8 @@ en:
proposal_question_example_html: "Debe ser resumida en una pregunta cuya respuesta sea Sí o No. <em>Ej. '¿Está usted de acuerdo en peatonalizar la calle Mayor?'</em>"
proposal_text: Initial text for proposal
proposal_external_url: Link to additional documentation
proposal_external_video_url: "Enlace a vídeo externo"
proposal_external_video_url_note: "Puedes añadir un enlace a YouTube o Vimeo"
proposal_summary: "Proposal summary"
proposal_summary_note: "200 chars. maximum"
proposal_responsible_name: "First and last name of the person making this proposal"

View File

@@ -180,6 +180,8 @@ es:
proposal_summary_note: "(máximo 200 caracteres)"
proposal_text: Texto desarrollado de la propuesta
proposal_external_url: Enlace a documentación adicional
proposal_external_video_url: "Enlace a vídeo externo"
proposal_external_video_url_note: "Puedes añadir un enlace a YouTube o Vimeo"
proposal_responsible_name: "Nombre y apellidos de la persona que hace esta propuesta"
proposal_responsible_name_note: "(individualmente o como representante de un colectivo; no se mostrará públicamente)"
tags_label: Temas

View File

@@ -0,0 +1,7 @@
class AddMoreIndexesForAhoy < ActiveRecord::Migration
def change
add_index :ahoy_events, [:name, :time]
add_index :visits, [:started_at]
end
end

View File

@@ -0,0 +1,19 @@
class AddsIndexes < ActiveRecord::Migration
def change
add_index :debates, :author_id
add_index :debates, [:author_id, :hidden_at]
add_index :proposals, :author_id
add_index :proposals, [:author_id, :hidden_at]
add_index :proposals, :cached_votes_up
add_index :proposals, :confidence_score
add_index :proposals, :hidden_at
add_index :proposals, :hot_score
add_index :settings, :key
add_index :verified_users, :document_number
add_index :verified_users, :phone
add_index :verified_users, :email
end
end

View File

@@ -0,0 +1,11 @@
class AddIndexesForSearches < ActiveRecord::Migration
def change
add_index :debates, :title
add_index :debates, :description
add_index :proposals, :title
add_index :proposals, :question
add_index :proposals, :summary
add_index :proposals, :description
end
end

View File

@@ -11,11 +11,10 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150914173834) do
ActiveRecord::Schema.define(version: 20150914184018) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "unaccent"
create_table "addresses", force: :cascade do |t|
t.integer "user_id"
@@ -52,6 +51,7 @@ ActiveRecord::Schema.define(version: 20150914173834) do
t.string "ip"
end
add_index "ahoy_events", ["name", "time"], name: "index_ahoy_events_on_name_and_time", using: :btree
add_index "ahoy_events", ["time"], name: "index_ahoy_events_on_time", using: :btree
add_index "ahoy_events", ["user_id"], name: "index_ahoy_events_on_user_id", using: :btree
add_index "ahoy_events", ["visit_id"], name: "index_ahoy_events_on_visit_id", using: :btree
@@ -105,13 +105,17 @@ ActiveRecord::Schema.define(version: 20150914173834) do
t.integer "confidence_score", default: 0
end
add_index "debates", ["author_id", "hidden_at"], name: "index_debates_on_author_id_and_hidden_at", using: :btree
add_index "debates", ["author_id"], name: "index_debates_on_author_id", using: :btree
add_index "debates", ["cached_votes_down"], name: "index_debates_on_cached_votes_down", using: :btree
add_index "debates", ["cached_votes_score"], name: "index_debates_on_cached_votes_score", using: :btree
add_index "debates", ["cached_votes_total"], name: "index_debates_on_cached_votes_total", using: :btree
add_index "debates", ["cached_votes_up"], name: "index_debates_on_cached_votes_up", using: :btree
add_index "debates", ["confidence_score"], name: "index_debates_on_confidence_score", using: :btree
add_index "debates", ["description"], name: "index_debates_on_description", using: :btree
add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree
add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree
add_index "debates", ["title"], name: "index_debates_on_title", using: :btree
create_table "delayed_jobs", force: :cascade do |t|
t.integer "priority", default: 0, null: false
@@ -166,7 +170,7 @@ ActiveRecord::Schema.define(version: 20150914173834) do
create_table "locks", force: :cascade do |t|
t.integer "user_id"
t.integer "tries", default: 0
t.datetime "locked_until", default: '2015-09-10 13:46:11', null: false
t.datetime "locked_until", default: '2015-09-11 17:24:30', null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
@@ -209,11 +213,24 @@ ActiveRecord::Schema.define(version: 20150914173834) do
t.text "summary"
end
add_index "proposals", ["author_id", "hidden_at"], name: "index_proposals_on_author_id_and_hidden_at", using: :btree
add_index "proposals", ["author_id"], name: "index_proposals_on_author_id", using: :btree
add_index "proposals", ["cached_votes_up"], name: "index_proposals_on_cached_votes_up", using: :btree
add_index "proposals", ["confidence_score"], name: "index_proposals_on_confidence_score", using: :btree
add_index "proposals", ["description"], name: "index_proposals_on_description", using: :btree
add_index "proposals", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree
add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree
add_index "proposals", ["question"], name: "index_proposals_on_question", using: :btree
add_index "proposals", ["summary"], name: "index_proposals_on_summary", using: :btree
add_index "proposals", ["title"], name: "index_proposals_on_title", using: :btree
create_table "settings", force: :cascade do |t|
t.string "key"
t.string "value"
end
add_index "settings", ["key"], name: "index_settings_on_key", using: :btree
create_table "simple_captcha_data", force: :cascade do |t|
t.string "key", limit: 40
t.string "value", limit: 6
@@ -296,6 +313,10 @@ ActiveRecord::Schema.define(version: 20150914173834) do
t.datetime "updated_at", null: false
end
add_index "verified_users", ["document_number"], name: "index_verified_users_on_document_number", using: :btree
add_index "verified_users", ["email"], name: "index_verified_users_on_email", using: :btree
add_index "verified_users", ["phone"], name: "index_verified_users_on_phone", using: :btree
create_table "visits", id: :uuid, default: nil, force: :cascade do |t|
t.uuid "visitor_id"
t.string "ip"
@@ -324,6 +345,7 @@ ActiveRecord::Schema.define(version: 20150914173834) do
t.datetime "started_at"
end
add_index "visits", ["started_at"], name: "index_visits_on_started_at", using: :btree
add_index "visits", ["user_id"], name: "index_visits_on_user_id", using: :btree
create_table "votes", force: :cascade do |t|

30
lib/score_calculator.rb Normal file
View File

@@ -0,0 +1,30 @@
module ScoreCalculator
EPOC = Time.new(2015, 6, 15)
COMMENT_WEIGHT = 1.0/20 # 1 positive vote / x comments
TIME_UNIT = 12.hours.to_f
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.now) - EPOC).to_f
(((offset * sign) + (seconds/TIME_UNIT)) * 10000000).round
end
def self.confidence_score(votes_total, votes_up)
return 0 unless votes_total > 0
votes_total = votes_total.to_f
votes_up = votes_up.to_f
votes_down = votes_total - votes_up
score = votes_up - votes_down
score * (votes_up / votes_total) * 100
end
end

View File

@@ -22,4 +22,13 @@ feature "Highlights" do
expect('worst proposal 60').to appear_before('worst debate 50')
end
scenario 'create debate and create proposal links' do
login_as(create(:user))
visit highlights_path
expect(page).to have_link("Start a proposal")
expect(page).to have_link("Start a debate")
end
end

View File

@@ -191,7 +191,7 @@ describe Debate do
end
it "increases for debates with more comments" do
more_comments = create(:debate, :with_hot_score, created_at: now, comments_count: 10)
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
end
@@ -232,8 +232,8 @@ describe Debate do
it "increases with comments" do
previous = debate.hot_score
Comment.create(user: create(:user), commentable: debate, body: 'foo')
expect(previous).to be < 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
@@ -244,16 +244,16 @@ describe Debate do
debate = create(:debate, :with_confidence_score, cached_votes_up: 100, cached_votes_score: 100, cached_votes_total: 100)
expect(debate.confidence_score).to eq(10000)
debate = create(:debate, :with_confidence_score, cached_votes_up: 0, cached_votes_score: -100, cached_votes_total: 100)
debate = create(:debate, :with_confidence_score, cached_votes_up: 0, cached_votes_total: 100)
expect(debate.confidence_score).to eq(0)
debate = create(:debate, :with_confidence_score, cached_votes_up: 50, cached_votes_score: 50, cached_votes_total: 100)
expect(debate.confidence_score).to eq(2500)
debate = create(:debate, :with_confidence_score, cached_votes_up: 75, cached_votes_total: 100)
expect(debate.confidence_score).to eq(3750)
debate = create(:debate, :with_confidence_score, cached_votes_up: 500, cached_votes_score: 500, cached_votes_total: 1000)
expect(debate.confidence_score).to eq(25000)
debate = create(:debate, :with_confidence_score, cached_votes_up: 750, cached_votes_total: 1000)
expect(debate.confidence_score).to eq(37500)
debate = create(:debate, :with_confidence_score, cached_votes_up: 10, cached_votes_score: -80, cached_votes_total: 100)
debate = create(:debate, :with_confidence_score, cached_votes_up: 10, cached_votes_total: 100)
expect(debate.confidence_score).to eq(-800)
end
@@ -396,4 +396,4 @@ describe Debate do
end
end
end

View File

@@ -142,4 +142,85 @@ describe Proposal do
end
end
end
end
describe '#hot_score' do
let(:now) { Time.now }
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
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
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
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
end
it "decays in older proposals, even if they have more votes" do
older_more_voted = create(:proposal, :with_hot_score, created_at: now - 2.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
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.now), true) }
expect(previous).to be < proposal.reload.hot_score
end
it "increases with comments" do
previous = proposal.hot_score
25.times{ Comment.create(user: create(:user), commentable: proposal, body: 'foobarbaz') }
expect(previous).to be < proposal.reload.hot_score
end
end
end
describe "#confidence_score" do
it "takes into account votes" do
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 100)
expect(proposal.confidence_score).to eq(10000)
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 0)
expect(proposal.confidence_score).to eq(0)
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 75)
expect(proposal.confidence_score).to eq(7500)
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 750)
expect(proposal.confidence_score).to eq(75000)
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 10)
expect(proposal.confidence_score).to eq(1000)
end
describe 'actions which affect it' do
let(:proposal) { create(:proposal, :with_confidence_score) }
it "increases with like" do
previous = proposal.confidence_score
5.times { proposal.register_vote(create(:user, verified_at: Time.now), true) }
expect(previous).to be < proposal.confidence_score
end
end
end
end