Commit 046756e4dfafa863b7c8fc305227bcaf42acb452

Authored by Hugo Melo
1 parent 70917f62

Create script for searching and fixing people points

Showing 1 changed file with 156 additions and 0 deletions   Show diff stats
script/check_merit_actions_vs_points.rb 0 → 100644
... ... @@ -0,0 +1,156 @@
  1 +#!/usr/bin/env ruby
  2 +# encoding: UTF-8
  3 +
  4 +#
  5 +# This script was created for ensuring all the actions observed
  6 +# by merit for pontuation was judged and pontuated accordingly
  7 +# It checks the merit_actions registers for each action(model
  8 +# create or destroy) and recreates it
  9 +#
  10 +
  11 +require 'csv'
  12 +
  13 +class ProcessObserver
  14 + def update(changed_data)
  15 + merit = changed_data[:merit_object]
  16 + if merit.kind_of?(Merit::Score::Point)
  17 + action = Merit::Action.find(changed_data[:merit_action_id])
  18 + new_date = YAML.load(action.target_data).created_at
  19 + action.update_attribute(:created_at, new_date)
  20 + merit.update_attribute(:created_at, new_date)
  21 + end
  22 + end
  23 +end
  24 +
  25 +def create_action(obj, index, count)
  26 + target_model = obj.class.base_class.name.downcase
  27 + action = Merit::Action.find_by_target_id_and_target_model_and_action_method(obj.id, target_model, 'create')
  28 + if action.nil?
  29 + puts "#{index}/#{count} Create merit action for #{target_model} #{obj.id}"
  30 + begin
  31 + obj.new_merit_action(:create)
  32 + rescue Exception => e
  33 + puts "Could not be create: #{e.message}"
  34 + end
  35 + end
  36 +end
  37 +
  38 +def recreate_actions person, objects, category
  39 + puts "Recreating actions for #{person.identifier} on model #{objects.first.class.base_class.name}"
  40 + actions = Merit::Action.where(target_id: objects, target_model: objects.first.class.base_class.name.downcase, action_method: 'create')
  41 + Merit::Score::Point.where(action_id: actions).destroy_all
  42 + actions.destroy_all
  43 + # erase remaining points if any (can be wrong on destroy cases ?)
  44 + person.score_points.where(score_id: Merit::Score.where(category: category)).destroy_all
  45 + count = objects.count
  46 + objects.each_with_index{ |obj, index| create_action(obj, index, count) }
  47 +end
  48 +
  49 +def calc_points categorization, objects
  50 + rule = Merit::PointRules::AVAILABLE_RULES[categorization.point_type.name.to_sym]
  51 + return 0 if rule.nil?
  52 +
  53 + sum = objects.map{|o| rule[:value].respond_to?(:call) ? rule[:value].call(o) : rule[:value] }.sum
  54 + return sum * categorization.weight
  55 +end
  56 +
  57 +# avoid updating level on every action for increasing performance
  58 +Merit.observers.delete('RankObserver')
  59 +
  60 +Merit.observers << 'ProcessObserver'
  61 +
  62 +class Article < ActiveRecord::Base
  63 + def self.text_article_types
  64 + ['ProposalsDiscussionPlugin::Proposal']
  65 + end
  66 +end
  67 +
  68 +puts "Creaning up points from actions which don't exist"
  69 +Merit::Score::Point.includes(:action).find_each(batch_size: 100) do |point|
  70 + point.destroy if point.action.nil?
  71 +end
  72 +
  73 +Environment.all.each do |environment|
  74 + puts "Process environment #{environment.name}"
  75 +
  76 + Merit::AppPointRules.clear
  77 + Merit::AppBadgeRules.clear
  78 + Merit::AppPointRules.merge!(Merit::PointRules.new(environment).defined_rules)
  79 + Merit::AppBadgeRules.merge!(Merit::BadgeRules.new(environment).defined_rules)
  80 +
  81 + group_control = YAML.load(File.read(File.join(Rails.root,'tmp','control_group.yml'))) if File.exist?(File.join(Rails.root,'tmp','control_group.yml'))
  82 + conditions = group_control.nil? ? {} : {:identifier => group_control.map{|k,v| v['profiles']}.flatten}
  83 + people_count = environment.people.where(conditions).count
  84 + person_index = 0
  85 + remaining_wrong_points = []
  86 + puts "Analising environment people"
  87 + environment.people.find_each(:conditions => conditions) do |person|
  88 + person_index += 1
  89 + profile_ids = GamificationPlugin::PointsCategorization.uniq.pluck(:profile_id)
  90 + profile_ids.keep_if { |item| group_control.keys.include?(item) } unless group_control.nil?
  91 + profile_ids.each do |profile_id|
  92 + profile = Profile.where(id: profile_id).first
  93 + if profile.nil?
  94 + profile_name = 'generic'
  95 + # person actions
  96 + person_articles = Article.where(author_id: person.id)
  97 + comments = Comment.where(author_id: person.id)
  98 + votes = Vote.for_voter(person)
  99 + follows = ArticleFollower.where(person_id: person.id)
  100 + else
  101 + profile_name = profile.identifier
  102 + #person actions
  103 + person_articles = Article.where(author_id: person.id, profile_id: profile)
  104 + comments = Comment.where(author_id: person.id, source_id: profile.articles)
  105 + general_votes = Vote.for_voter(person)
  106 + votes = general_votes.where("(voteable_type = 'Article' and voteable_id in (?)) or (voteable_type = 'Comment' and voteable_id in (?))",profile.articles, Comment.where(source_type: "Article", source_id: profile.articles))
  107 + follows = ArticleFollower.where(person_id: person.id, article_id: profile.articles)
  108 + end
  109 + # received actions
  110 + comments_received = Comment.where(:source_id => person_articles)
  111 + votes_received = Vote.where("(voteable_type = 'Article' and voteable_id in (?)) or (voteable_type = 'Comment' and voteable_id in (?))",person_articles, person.comments)
  112 + follows_received = ArticleFollower.where(:article_id => person_articles)
  113 +
  114 + puts "#{person_index}/#{people_count} - Analising points for #{person.identifier} on #{profile_name}"
  115 + puts "Proposed #{person_articles.count} times, Commented #{comments.count} times, Voted #{votes.count} times, Followed #{follows.count} times"
  116 + puts "Received #{votes_received.count} votes, #{comments_received.count} comments, #{follows_received.count} follows\n"
  117 +
  118 + scope_by_type = {
  119 + article_author: person_articles, comment_author: comments, vote_voter: votes, follower: follows,
  120 + comment_article_author: comments_received, vote_voteable_author: votes_received, followed_article_author: follows_received
  121 + }
  122 +
  123 + puts "Points:"
  124 + scope_by_type.each do |type, scope|
  125 + c = GamificationPlugin::PointsCategorization.for_type(type).where(profile_id: profile_id).joins(:point_type).first
  126 + points = calc_points c, scope
  127 + puts "On #{c.point_type.name} it should have #{points} and have #{person.points(category: c.id.to_s)} "
  128 + if points != person.points(category: c.id.to_s)
  129 + recreate_actions person, scope, c.id.to_s
  130 + points = calc_points c, scope
  131 + puts "after recreating points the person has: #{person.reload.points(category: c.id.to_s)} and should have #{points}"
  132 + remaining_wrong_points << [person.identifier, person.name, scope.first.class.base_class.name, profile_name, c.id, c.point_type.name, scope.count*c.weight, person.points(category: c.id.to_s)] if points != person.points(category: c.id.to_s)
  133 + end
  134 + end
  135 + puts
  136 + end
  137 + puts "Updating #{person.identifier} level\n"
  138 + person.update_attribute(:level, person.gamification_plugin_calculate_level)
  139 + end
  140 +
  141 + # write to the spreadsheet the person points that couldn't regulate
  142 + unless remaining_wrong_points.blank?
  143 + CSV.open( "gamification_points_out_expectation.csv", 'w' ) do |csv|
  144 + csv << ['identifier', 'name', 'action', 'profile', 'category id', 'category type', 'should have', 'have']
  145 + remaining_wrong_points.each do |line|
  146 + csv << line
  147 + end
  148 + end
  149 + end
  150 +
  151 + if remaining_wrong_points.count
  152 + puts "Finished. There was #{remaining_wrong_points.count} people/pontuation types with errors after check and fix. Please check the created spreadsheet."
  153 + else
  154 + puts "Finished. There was no errors after checking. \o/ Pontuation seems to be ok!"
  155 + end
  156 +end
... ...