From a31a1e8c35edfdc3d7af72cd0e6d1416de3c6100 Mon Sep 17 00:00:00 2001
From: Mateus Rambo Strey <mars11@inf.ufpr.br>
Date: Thu, 25 Feb 2016 11:06:17 -0300
Subject: [PATCH] working reputation reference calculation

---
 app/models/score.rb                           |  9 +++
 app/models/score_user_category.rb             | 27 +++++++++
 app/models/user.rb                            | 11 +++-
 app/services/score_calculator_service.rb      | 47 ++++++++++++++++
 app/workers/score_calculator_worker.rb        | 56 ++-----------------
 db/migrate/20160222144157_create_scores.rb    |  2 +-
 .../20160222144216_create_user_categories.rb  |  1 +
 .../20160225101700_change_users_add_score.rb  |  6 ++
 db/seeds/scores.rb                            |  4 +-
 9 files changed, 108 insertions(+), 55 deletions(-)
 create mode 100644 app/services/score_calculator_service.rb
 create mode 100644 db/migrate/20160225101700_change_users_add_score.rb

diff --git a/app/models/score.rb b/app/models/score.rb
index cd2f1acf5..508061b5f 100644
--- a/app/models/score.rb
+++ b/app/models/score.rb
@@ -4,4 +4,13 @@ class Score < ActiveRecord::Base
 
   validates_presence_of :name, :code, :weight
   validates_uniqueness_of :code
+
+  def max_value
+    score_user_categories.maximum(:value)
+  end
+
+  def category_value(category)
+    score = score_user_categories.where(score: self, user_category: category).first
+    (score.nil?) ? nil : score.value
+  end
 end
diff --git a/app/models/score_user_category.rb b/app/models/score_user_category.rb
index 9d701e717..1ff802069 100644
--- a/app/models/score_user_category.rb
+++ b/app/models/score_user_category.rb
@@ -4,4 +4,31 @@ class ScoreUserCategory < ActiveRecord::Base
 
   validates_presence_of :score, :user_category, :value
   validates_uniqueness_of :user_category, scope: :score
+
+  after_commit :calculate_reference
+
+  private
+
+  def calculate_reference
+    return 0 if Score.count < 9 || !reputation_hash.values.all? { |x| !x.nil? }
+
+    reference = ScoreCalculatorService.new(self).reputation(reputation_hash)
+
+    user_category.update(reference: reference) if reference.is_a? Float
+  end
+
+  def reputation_hash
+    {
+      'reputation_submitted': Score.where(code: 'reputation_submitted').first.category_value(user_category),
+      'reputation_reviews_average_score': Score.where(code: 'reputation_reviews_average_score').first.category_value(user_category),
+      'reputation_added_in_best_collections': Score.where(code: 'reputation_added_in_best_collections').first.category_value(user_category),
+      'reputation_best_score': Score.where(code: 'reputation_best_score').first.category_value(user_category),
+      'reputation_average_score': Score.where(code: 'reputation_average_score').first.category_value(user_category),
+      'reputation_reviews_rate': Score.where(code: 'reputation_reviews_rate').first.category_value(user_category),
+      # 'reputation_confirmed_complaints': Score.where(code: 'reputation_confirmed_complaints').first.category_value(user_category),
+      'reputation_followers': Score.where(code: 'reputation_followers').first.category_value(user_category),
+      'reputation_submitted_recently': Score.where(code: 'reputation_submitted_recently').first.category_value(user_category),
+      'reputation_collection_score': Score.where(code: 'reputation_collection_score').first.category_value(user_category)
+    }
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index b9b89e6e7..04022015a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -45,10 +45,19 @@ class User < ActiveRecord::Base
   end
 
   def review_approval_average
-    array = reviews.map { |review| review.rates_percentage }
+    array = reviews.map(&:rates_percentage)
     array.inject(0.0) { |sum, el| sum + el } / array.size
   end
 
+  def learning_objects_in_best_collections
+    average = Collection.average(:score)
+    collections.where(:score > average).count
+  end
+
+  def learning_objects_submitted_recently
+    learning_objects.where(:created_at > (Time.now - 2.months)).count
+  end
+
   private
 
   def default_role
diff --git a/app/services/score_calculator_service.rb b/app/services/score_calculator_service.rb
new file mode 100644
index 000000000..9dc68a650
--- /dev/null
+++ b/app/services/score_calculator_service.rb
@@ -0,0 +1,47 @@
+class ScoreCalculatorService
+
+  def initialize(object)
+    @object = object
+    @class = @object.class.name
+  end
+
+  def calculate
+    case @class
+    when 'User' then reputation
+    end
+  end
+
+  def reputation(values = nil)
+    values = reputation_hash if values.nil?
+
+    sum = 0
+
+    Score.all.find_each do |score|
+      sum += (values[score.code.to_sym].to_f / score.max_value.to_f) * score.weight.to_f unless score.max_value.to_f == 0
+    end
+
+    sum.to_f / sum_weights.to_f
+  end
+
+  private
+
+  def reputation_hash
+    {
+      'reputation_submitted': @object.learning_objects.count,
+      'reputation_reviews_average_score': @object.learning_objects.review_ratings_average,
+      'reputation_added_in_best_collections': @object.learning_objects_in_best_collections,
+      'reputation_best_score': @object.learning_objects.maximum(:score),
+      'reputation_average_score': @object.learning_objects.average(:score),
+      'reputation_reviews_rate': @object.review_approval_average,
+      # 'reputation_confirmed_complaints': ,
+      'reputation_followers': @object.followers.count,
+      'reputation_submitted_recently': @object.learning_objects_submitted_recently,
+      'reputation_collection_score': @object.where(privacy: 'public').average(:score)
+    }
+  end
+
+  def sum_weights
+    Score.pluck(:weight).inject(0){|sum,r| sum + r }.to_f
+  end
+
+end
diff --git a/app/workers/score_calculator_worker.rb b/app/workers/score_calculator_worker.rb
index 5654058c0..82977994b 100644
--- a/app/workers/score_calculator_worker.rb
+++ b/app/workers/score_calculator_worker.rb
@@ -2,59 +2,13 @@ class ScoreCalculatorWorker
   include Sidekiq::Worker
   sidekiq_options queue: :score
 
-  def perform(rid)
-    object = LearningObject.find(rid)
+  def perform(id, object_class)
+    return false unless %w(User LearningObject Collection)
 
-    unless object.blank?
-        # Weights to score. Sum must be 1000
-        weights = {
-            "thumbnail": 250,
-            "description": 150,
-            "likes": 250,
-            "views": 150,
-            "downloads":200
-          }
+    object = object_class.constantize.find(id)
 
-    score = 0
+    score = ScoreCalculatorService.new(object).calculate
 
-    # 250 points if it has thumbnail
-    score += weights[:thumbnail] unless object.thumbnail.blank?
-
-    # 150 points if it has description
-    score += weights[:description] unless object.description.blank?
-
-    # range to 250 points, for normalized likes ( maxLikes/actualLike => [0..1] )
-    score += divide_by_max(
-                            LearningObject.count('Likes', object),
-                            LearningObject.max('Likes'),
-                            weights[:likes]
-                          )
-
-    # range to 200 points, for normalized views ( maxLikes/actualLike => [0..1] )
-    score += divide_by_max(
-                            LearningObject.count('Views', object),
-                            LearningObject.max('Views'),
-                            weights[:views]
-                          )
-
-    # downloads
-    score += divide_by_max(
-                            LearningObject.count('Downloads', object),
-                            LearningObject.max('Downloads'),
-                            weights[:downloads]
-                          )
-
-    LearningObject.update_property(object, 'score', score)
-    end
-  end
-
-  private
-
-  def divide_by_max(i, max, weight)
-    if (i > 0 && max > 0) && (i <= max)
-      (i / max) * weight
-    else
-      0
-    end
+    object.update(score: score)
   end
 end
diff --git a/db/migrate/20160222144157_create_scores.rb b/db/migrate/20160222144157_create_scores.rb
index d92548521..736b07385 100644
--- a/db/migrate/20160222144157_create_scores.rb
+++ b/db/migrate/20160222144157_create_scores.rb
@@ -3,7 +3,7 @@ class CreateScores < ActiveRecord::Migration
     create_table :scores do |t|
       t.string :name
       t.string :code
-      t.integer :weight
+      t.float :weight
       t.boolean :active, default: true
 
       t.timestamps null: false
diff --git a/db/migrate/20160222144216_create_user_categories.rb b/db/migrate/20160222144216_create_user_categories.rb
index 0b1594c62..fa97c78f2 100644
--- a/db/migrate/20160222144216_create_user_categories.rb
+++ b/db/migrate/20160222144216_create_user_categories.rb
@@ -2,6 +2,7 @@ class CreateUserCategories < ActiveRecord::Migration
   def change
     create_table :user_categories do |t|
       t.string :name
+      t.float :reference
 
       t.timestamps null: false
     end
diff --git a/db/migrate/20160225101700_change_users_add_score.rb b/db/migrate/20160225101700_change_users_add_score.rb
new file mode 100644
index 000000000..ac9d75bb8
--- /dev/null
+++ b/db/migrate/20160225101700_change_users_add_score.rb
@@ -0,0 +1,6 @@
+class ChangeUsersAddScore < ActiveRecord::Migration
+  def change
+    add_reference :users, :user_category, index: true
+    add_column :users, :score, :float, default: 0
+  end
+end
diff --git a/db/seeds/scores.rb b/db/seeds/scores.rb
index 6668e3c17..9b5ac52ea 100644
--- a/db/seeds/scores.rb
+++ b/db/seeds/scores.rb
@@ -7,7 +7,7 @@ categories = [
   { name: 'Amador' },
   { name: 'Profissional' },
   { name: 'Expert' }
-]
+].reverse
 
 # reputation
 reputations = [
@@ -29,7 +29,7 @@ values = [
   [   50,  3.8,   20,  550,  350,   85,  250,   15,  600],  # Amador
   [  100,  4.2,   40,  600,  450,   90, 1000,   25,  750],  # Profissional
   [  250,  4.9,   80,  680,  550,   95, 5000,   35,  800]   # Expert
-]
+].reverse
 
 # iterate to make associations
 categories.each_with_index do |c, i|
-- 
GitLab