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 @@ |
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 | 37 | person_down_votes = person.comments.joins(:votes).where('vote < 0').count + person_articles.joins(:votes).where('vote < 0').count |
38 | 38 | person_comments = person.comments.count |
39 | 39 | person_followers = (person.following_articles & person.article_followers.where(article_id: person_articles)).count |
40 | + votes = Vote.for_voter(person).count | |
40 | 41 | else |
41 | 42 | person_articles = profile.articles.where(:author_id => person.id) |
42 | 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 | 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 | 45 | person_comments = person.comments.where(:source_id => profile.articles).count |
45 | 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 | 49 | end |
47 | 50 | quantities_values = [ |
48 | - Vote.for_voter(person).count, | |
51 | + votes, | |
49 | 52 | person.friends.count, |
50 | 53 | person_up_votes, |
51 | 54 | person_down_votes, | ... | ... |
script/process_merit_rules.rb
... | ... | @@ -35,13 +35,16 @@ end |
35 | 35 | # person.sash.destroy unless person.sash.nil? |
36 | 36 | #end |
37 | 37 | |
38 | +# avoid updating level on every action for increasing performance | |
39 | +Merit.observers.delete('RankObserver') | |
40 | + | |
38 | 41 | Merit.observers << 'ProcessObserver' |
39 | 42 | |
40 | 43 | class Article < ActiveRecord::Base |
41 | 44 | def self.text_article_types |
42 | 45 | # ['TextArticle', 'TextileArticle', 'TinyMceArticle', 'ProposalsDiscussionPlugin::Proposal'] |
43 | 46 | ['ProposalsDiscussionPlugin::Proposal'] |
44 | - end | |
47 | + end | |
45 | 48 | end |
46 | 49 | |
47 | 50 | Environment.all.each do |environment| |
... | ... | @@ -56,7 +59,7 @@ Environment.all.each do |environment| |
56 | 59 | article_index = 0 |
57 | 60 | |
58 | 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 | 63 | article_index += 1 |
61 | 64 | puts "Analising article #{article_index} of #{article_count}" |
62 | 65 | create_action(article, article_index, article_count) | ... | ... |