Commit e8437fd41151d0213bcb6ce142d127480c5ffd6c
Exists in
master
and in
1 other branch
Merge branch 'master' of softwarepublico.gov.br:noosfero-plugins/gamification
Showing
3 changed files
with
170 additions
and
3 deletions
Show diff stats
@@ -0,0 +1,161 @@ | @@ -0,0 +1,161 @@ | ||
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 | + end | ||
138 | + | ||
139 | + # update everyone's level after the whole pontuation, | ||
140 | + # which is much faster than on every created action | ||
141 | + environment.people.find_each(batch_size: 100) do |person| | ||
142 | + puts "Updating #{person.identifier} level\n" | ||
143 | + person.update_attribute(:level, person.gamification_plugin_calculate_level) | ||
144 | + end | ||
145 | + | ||
146 | + # write to the spreadsheet the person points that couldn't regulate | ||
147 | + unless remaining_wrong_points.blank? | ||
148 | + CSV.open( "gamification_points_out_expectation.csv", 'w' ) do |csv| | ||
149 | + csv << ['identifier', 'name', 'action', 'profile', 'category id', 'category type', 'should have', 'have'] | ||
150 | + remaining_wrong_points.each do |line| | ||
151 | + csv << line | ||
152 | + end | ||
153 | + end | ||
154 | + end | ||
155 | + | ||
156 | + if remaining_wrong_points.count | ||
157 | + puts "Finished. There was #{remaining_wrong_points.count} people/pontuation types with errors after check and fix. Please check the created spreadsheet." | ||
158 | + else | ||
159 | + puts "Finished. There was no errors after checking. \o/ Pontuation seems to be ok!" | ||
160 | + end | ||
161 | +end |
script/export_ranking.rb
@@ -37,15 +37,18 @@ profile_ids.each do |profile_id| | @@ -37,15 +37,18 @@ profile_ids.each do |profile_id| | ||
37 | person_down_votes = person.comments.joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count | 37 | person_down_votes = person.comments.joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count |
38 | person_comments = person.comments.count | 38 | person_comments = person.comments.count |
39 | person_followers = (person.following_articles & person.article_followers.where(article_id: person_articles)).count | 39 | person_followers = (person.following_articles & person.article_followers.where(article_id: person_articles)).count |
40 | + votes = Vote.for_voter(person).count | ||
40 | else | 41 | else |
41 | person_articles = profile.articles.where(:author_id => person.id) | 42 | person_articles = profile.articles.where(:author_id => person.id) |
42 | person_up_votes = person.comments.where(:source_id => profile.articles).joins(:votes).where('vote > 0').count + person_articles.joins(:votes).where('vote > 0').count | 43 | person_up_votes = person.comments.where(:source_id => profile.articles).joins(:votes).where('vote > 0').count + person_articles.joins(:votes).where('vote > 0').count |
43 | person_down_votes = person.comments.where(:source_id => profile.articles).joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count | 44 | person_down_votes = person.comments.where(:source_id => profile.articles).joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count |
44 | person_comments = person.comments.where(:source_id => profile.articles).count | 45 | person_comments = person.comments.where(:source_id => profile.articles).count |
45 | person_followers = (person.following_articles & person.article_followers.where(article_id: profile.articles)).count | 46 | person_followers = (person.following_articles & person.article_followers.where(article_id: profile.articles)).count |
47 | + the_votes = Vote.for_voter(person) | ||
48 | + votes = the_votes.where(voteable_type: 'Article', voteable_id: profile.articles).count + the_votes.where(voteable_type: 'Comment', voteable_id: Comment.where(source_type: ["ProposalsDiscussionPlugin::Proposal", "Article"], source_id: profile.articles)).count | ||
46 | end | 49 | end |
47 | quantities_values = [ | 50 | quantities_values = [ |
48 | - Vote.for_voter(person).count, | 51 | + votes, |
49 | person.friends.count, | 52 | person.friends.count, |
50 | person_up_votes, | 53 | person_up_votes, |
51 | person_down_votes, | 54 | person_down_votes, |
script/process_merit_rules.rb
@@ -35,13 +35,16 @@ end | @@ -35,13 +35,16 @@ end | ||
35 | # person.sash.destroy unless person.sash.nil? | 35 | # person.sash.destroy unless person.sash.nil? |
36 | #end | 36 | #end |
37 | 37 | ||
38 | +# avoid updating level on every action for increasing performance | ||
39 | +Merit.observers.delete('RankObserver') | ||
40 | + | ||
38 | Merit.observers << 'ProcessObserver' | 41 | Merit.observers << 'ProcessObserver' |
39 | 42 | ||
40 | class Article < ActiveRecord::Base | 43 | class Article < ActiveRecord::Base |
41 | def self.text_article_types | 44 | def self.text_article_types |
42 | # ['TextArticle', 'TextileArticle', 'TinyMceArticle', 'ProposalsDiscussionPlugin::Proposal'] | 45 | # ['TextArticle', 'TextileArticle', 'TinyMceArticle', 'ProposalsDiscussionPlugin::Proposal'] |
43 | ['ProposalsDiscussionPlugin::Proposal'] | 46 | ['ProposalsDiscussionPlugin::Proposal'] |
44 | - end | 47 | + end |
45 | end | 48 | end |
46 | 49 | ||
47 | Environment.all.each do |environment| | 50 | Environment.all.each do |environment| |
@@ -56,7 +59,7 @@ Environment.all.each do |environment| | @@ -56,7 +59,7 @@ Environment.all.each do |environment| | ||
56 | article_index = 0 | 59 | article_index = 0 |
57 | 60 | ||
58 | puts "Amount of articles '#{article_count}'" | 61 | puts "Amount of articles '#{article_count}'" |
59 | - environment.articles.where(:type => Article.text_article_types).find_each do |article| | 62 | + environment.articles.includes(:comments).where(:type => Article.text_article_types).find_each(batch_size: 100) do |article| |
60 | article_index += 1 | 63 | article_index += 1 |
61 | puts "Analising article #{article_index} of #{article_count}" | 64 | puts "Analising article #{article_index} of #{article_count}" |
62 | create_action(article, article_index, article_count) | 65 | create_action(article, article_index, article_count) |