Merge branch 'proposals' of github.com:AyuntamientoMadrid/participacion into proposals
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -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 %>
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
7
db/migrate/20150914181921_add_more_indexes_for_ahoy.rb
Normal file
7
db/migrate/20150914181921_add_more_indexes_for_ahoy.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class AddMoreIndexesForAhoy < ActiveRecord::Migration
|
||||
def change
|
||||
add_index :ahoy_events, [:name, :time]
|
||||
add_index :visits, [:started_at]
|
||||
end
|
||||
end
|
||||
|
||||
19
db/migrate/20150914182652_adds_indexes.rb
Normal file
19
db/migrate/20150914182652_adds_indexes.rb
Normal 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
|
||||
11
db/migrate/20150914184018_add_indexes_for_searches.rb
Normal file
11
db/migrate/20150914184018_add_indexes_for_searches.rb
Normal 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
|
||||
28
db/schema.rb
28
db/schema.rb
@@ -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
30
lib/score_calculator.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user