viking-pairity 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/exe/pairity ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pairity'
4
+
5
+
6
+ begin
7
+ Pairity::CLI.new.start
8
+ rescue Interrupt
9
+ puts
10
+ puts "Quitting Pairity..."
11
+ end
data/lib/pairity.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "pairity/version"
2
+ require "pairity/edge"
3
+ require "pairity/person"
4
+ require "pairity/pair_stats"
5
+ require "pairity/pair_saver"
6
+ require "pairity/pair_generator"
7
+ require "pairity/adjacency_matrix"
8
+ require "pairity/google_sync"
9
+ require "pairity/cli"
10
+ require "pairity/slackbot"
11
+ require "pairity/config"
12
+
13
+ module Pairity
14
+
15
+ end
@@ -0,0 +1,142 @@
1
+ require 'graph_matching'
2
+
3
+ module Pairity
4
+ class AdjacencyMatrix
5
+ attr_accessor :matrix, :han_solo
6
+
7
+ def initialize
8
+ @han_solo = Person.new(name: "Han Solo")
9
+ @people = [@han_solo]
10
+ @matrix = {}
11
+ end
12
+
13
+ def to_s
14
+ output = []
15
+ matrix.each do |pair, weight|
16
+ # next if pair.include?(@han_solo)
17
+ output << "#{pair} -> #{weight}"
18
+ end
19
+ output.join("\n")
20
+ end
21
+
22
+ def people(solo = false)
23
+ if @people.size.odd? || solo
24
+ @people.reject { |person| person.name == "Han Solo" }
25
+ else
26
+ @people
27
+ end
28
+ end
29
+
30
+ def matrix(solo = false)
31
+ if @people.size.odd? || solo
32
+ without_solo
33
+ else
34
+ @matrix
35
+ end
36
+ end
37
+
38
+ def set_ids(persons)
39
+ persons.each_with_index do |p, i|
40
+ p.id = i+1
41
+ end
42
+ end
43
+
44
+ def all_people
45
+ @people
46
+ end
47
+
48
+ def without_solo
49
+ @matrix.reject { |pair, edge| pair.include?(han_solo) }
50
+ end
51
+
52
+ def [](*args)
53
+ @matrix[args.sort]
54
+ end
55
+
56
+ def []=(*args, edge)
57
+ @matrix[args.sort] = edge
58
+ end
59
+
60
+ def weight_for_pairs(pairs)
61
+ pairs.inject(0) do |sum, pair|
62
+ sum += weight_for_pair(pair)
63
+ end
64
+ end
65
+
66
+ def weight_for_pair(pair)
67
+ @matrix[pair.sort].weight
68
+ end
69
+
70
+ def add_person(new_person)
71
+ @people.each do |person|
72
+ pair = [person, new_person].sort
73
+ @matrix[pair] = Edge.new
74
+ end
75
+ @people << new_person
76
+ end
77
+
78
+ def average_weight
79
+ people.combination(2).to_a.inject(0) do |sum, pair|
80
+ sum += self[*pair].weight
81
+ end / people.combination(2).size
82
+ end
83
+
84
+ def average_days
85
+ people.combination(2).to_a.inject(0) do |sum, pair|
86
+ sum += self[*pair].days
87
+ end / people.combination(2).size
88
+ end
89
+
90
+ def get_pairs(pairs, first)
91
+ return [] if pairs.empty?
92
+ pairs.first(first).map do |pair|
93
+ [pair, get_pairs(pairs_without_pair(pairs, pair), 1000).flatten]
94
+ end
95
+ end
96
+
97
+ def find_person(id)
98
+ @people.find { |p| p.id == id }
99
+ end
100
+
101
+ def optimal_pairs(nopes)
102
+ set_ids(people)
103
+ pairing_array = matrix.map do |pair, edge|
104
+ next if nopes.include?(pair.sort)
105
+ p1, p2 = pair
106
+ ids = [p1.id, p2.id].sort
107
+ [ids[0], ids[1], edge.weight * -1]
108
+ end
109
+
110
+ pairing_array.compact!
111
+
112
+ g = GraphMatching::Graph::WeightedGraph[*pairing_array]
113
+ m = g.maximum_weighted_matching(true)
114
+ m.edges.map do |pair|
115
+ [find_person(pair[0]), find_person(pair[1])]
116
+ end
117
+ end
118
+
119
+ def remove_person(person)
120
+ @matrix.reject! do |pair, weight|
121
+ pair.include?(person)
122
+ end
123
+ @people.delete(person)
124
+ end
125
+
126
+ def resistance(weight, pair)
127
+ tier_compensation = 0
128
+ if pair.all?{ |p| p.tier == 1} || pair.all?{ |p| p.tier == 3}
129
+ tier_compensation = 1.5
130
+ end
131
+ @matrix[pair.sort].resistance * weight + tier_compensation
132
+ end
133
+
134
+ def add_weight_to_pair(weight, pair)
135
+ @matrix[pair.sort].weight += resistance(weight, pair)
136
+ end
137
+
138
+ def add_day_to_pair(pair)
139
+ @matrix[pair.sort].days += 1
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,336 @@
1
+ require 'highline/import'
2
+ require 'terminal-table'
3
+ require 'rainbow'
4
+ require 'yaml'
5
+
6
+ module Pairity
7
+ class CLI
8
+ def initialize
9
+ @matrix = AdjacencyMatrix.new
10
+ @sync = GoogleSync.new(@matrix)
11
+ @generator = PairGenerator.new(@matrix)
12
+ @renderer = PairRenderer.new(@matrix)
13
+ load_google
14
+ end
15
+
16
+ def start
17
+ unless Config.configured?
18
+ slack_config
19
+ end
20
+
21
+ action_menu
22
+ end
23
+
24
+ def action_menu
25
+ puts
26
+ puts Rainbow("==== PAIRITY ====").white
27
+ choose do |menu|
28
+ menu.prompt = "What would you like to do?"
29
+ menu.choice("Generate Pairs") { generate_pairs }
30
+ menu.choice("Edit People") { edit_people }
31
+ # menu.choice("Edit Pair") { edit_pair }
32
+ menu.choice("Save Changes") { save_changes }
33
+ menu.choice("Open Google Sheet") { open_google_sheet }
34
+ menu.choice("Change Slack Channel") { change_channel }
35
+ end
36
+ end
37
+
38
+ def open_google_sheet
39
+ `open "#{@sync.sheet_url}"`
40
+
41
+ action_menu
42
+ end
43
+
44
+ def slack_config
45
+ puts "Let's set up Slack Integration."
46
+ Config.load
47
+ slack_webhook = ask("Please enter your Slack Webhook URL (more information: https://vikingcodeschool.slack.com/apps/new/A0F7XDUAZ-incoming-webhooks)") do |q|
48
+ q.validate = /hooks\.slack\.com\/services\//
49
+ end
50
+
51
+ channel = ask("What channel would you like to post to? (Don't write the #')")
52
+
53
+ Config.add(url: slack_webhook, channel: channel)
54
+ Config.save
55
+ end
56
+
57
+ def change_channel
58
+ puts "Let's set up Slack Integration."
59
+ Config.load
60
+
61
+ channel = ask("What channel would you like to post to? (Don't write the #')")
62
+
63
+ Config.add(channel: channel)
64
+ Config.save
65
+ puts "Channel changed to ##{channel}"
66
+ action_menu
67
+ end
68
+
69
+ def edit_people
70
+ choose do |menu|
71
+ menu.prompt = "Add or remove?"
72
+ menu.choice("Add") { add_people }
73
+ menu.choice("Remove") { remove_people }
74
+ menu.choice("Rename") { rename }
75
+ menu.choice("Change Tier") { change_tier }
76
+ end
77
+ end
78
+
79
+ def save_changes
80
+ @sync.save
81
+ action_menu
82
+ end
83
+
84
+ def rename
85
+ choice = choose_person
86
+
87
+ new_name = ask "What name would you like to give #{Rainbow(choice.name).white}?"
88
+
89
+ old_name = choice.name
90
+ choice.name = new_name
91
+
92
+ puts "#{Rainbow(old_name).white} shall henceforth be known as #{Rainbow(choice.name).white}!"
93
+
94
+ action_menu
95
+ end
96
+
97
+ def change_tier
98
+ choice = choose_person(tier: true)
99
+
100
+ tier = 2
101
+ choose do |menu|
102
+ menu.prompt = "Set #{Rainbow(choice.name).white} to what tier?"
103
+ menu.choice("Tier 1") { tier = 1 }
104
+ menu.choice("Tier 2") { tier = 2 }
105
+ menu.choice("Tier 3") { tier = 3 }
106
+ end
107
+
108
+ choice.tier = tier
109
+
110
+ puts "#{Rainbow(choice.name).white} is now tier #{tier}."
111
+
112
+ action_menu
113
+ end
114
+
115
+ def remove_people
116
+ choice = choose_person
117
+
118
+ answer = ask "Are you sure you would like to remove #{Rainbow(choice).white}?" do |q|
119
+ q.validate = /y|n/
120
+ end
121
+
122
+ if answer =~ /y/
123
+ @matrix.remove_person(choice)
124
+ end
125
+
126
+ puts "#{Rainbow(choice).white} has been removed."
127
+
128
+ action_menu
129
+ end
130
+
131
+ def choose_person(tier: false)
132
+ people = @matrix.people(true)
133
+ choice = nil
134
+ choose do |menu|
135
+ menu.prompt = "Select a person."
136
+ people.each_with_index do |person, index|
137
+ menu.choice(display_person(person, tier: tier)) { choice = people[index] }
138
+ end
139
+ end
140
+ choice
141
+ end
142
+
143
+ def display_person(person, tier: false)
144
+ output = []
145
+ output << "#{Rainbow(person.name).white}"
146
+ output << "-- Tier #{person.tier}" if tier
147
+ output.join(" ")
148
+ end
149
+
150
+ def add_people
151
+ names = ask "What are their names? (enter names separated by commas)"
152
+ names = names.split(",")
153
+ names.each do |name|
154
+ person = Person.new(name: name.strip)
155
+ @matrix.add_person(person)
156
+ puts "Added #{name.strip}!"
157
+ end
158
+ action_menu
159
+ end
160
+
161
+ def nope_pair
162
+ pair = choose_from_generated_pair
163
+
164
+ answer = ask "Are you sure you would like to nope today's #{display_pair_names(pair)} pairing?" do |q|
165
+ q.validate = /y|n/
166
+ end
167
+
168
+ if answer =~ /y/
169
+ p1, p2 = pair
170
+ @generator.nope(p1, p2)
171
+ puts "#{display_pair_names(pair)} have been 'Noped'!"
172
+ end
173
+
174
+ @generator.generate_pairs
175
+ pairs_menu
176
+ end
177
+
178
+ def choose_from_generated_pair
179
+ choice = nil
180
+
181
+ choose do |menu|
182
+ menu.prompt = "Who would you like to edit?"
183
+ @generator.pairs.each_with_index do |pair, index|
184
+ menu.choice(display_pair_names(pair)) { choice = index }
185
+ end
186
+ end
187
+
188
+ choice = @generator.pairs[choice]
189
+ end
190
+
191
+ def choose_pair
192
+ choice = nil
193
+
194
+ choose do |menu|
195
+ menu.prompt = "Who would you like to edit?"
196
+ all_combos.with_index do |pair, index|
197
+ menu.choice(display_pair_names(pair)) { choice = index }
198
+ end
199
+ end
200
+
201
+ choice = all_combos.to_a[choice]
202
+ end
203
+
204
+
205
+ def edit_pair
206
+ choice = choose_pair
207
+
208
+ choose do |menu|
209
+ menu.prompt = "How would you like to edit #{display_pair_names(choice)}"
210
+ menu.choice("Increase Pair Chance") { increase_chance(choice) }
211
+ menu.choice("Decrease Pair Chance") { decrease_chance(choice) }
212
+ menu.choice("Abolish Pair Chance") { condemn_pair(choice) }
213
+ end
214
+
215
+ end
216
+
217
+ def increase_chance(pair)
218
+ answer = ask "Are you sure you would like to increase the odds of #{display_pair_names(pair)} pairing?" do |q|
219
+ q.validate = /y|n/
220
+ end
221
+
222
+ if answer =~ /y/
223
+ p1, p2 = pair
224
+ @generator.resistance(p1, p2, 0.5)
225
+ puts "#{display_pair_names(pair)} are more likely to be paired."
226
+ end
227
+
228
+ action_menu
229
+ end
230
+
231
+ def decrease_chance(pair)
232
+ answer = ask "Are you sure you would like to decrease the odds of #{display_pair_names(pair)} pairing?" do |q|
233
+ q.validate = /y|n/
234
+ end
235
+
236
+ if answer =~ /y/
237
+ p1, p2 = pair
238
+ @generator.resistance(p1, p2, 2)
239
+ puts "#{display_pair_names(pair)} are less likely to be paired."
240
+ end
241
+
242
+ action_menu
243
+ end
244
+
245
+ def condemn_pair(pair)
246
+
247
+ answer = ask "Are you sure you would like to condemn #{display_pair_names(pair)}?" do |q|
248
+ q.validate = /y|n/
249
+ end
250
+
251
+ if answer =~ /y/
252
+ @generator.abolish_pairing(*pair)
253
+ puts "#{display_pair_names(pair)} will no longer be paired."
254
+ end
255
+
256
+ action_menu
257
+ end
258
+
259
+ def generate_pairs
260
+ @generator.generate_pairs
261
+ puts
262
+ pairs_menu
263
+ end
264
+
265
+ def pairs_menu
266
+ display_pairs
267
+ puts
268
+ choose do |menu|
269
+ menu.prompt = "What would you like to do?"
270
+ menu.choice("Save & Slack") { save_pairs }
271
+ menu.choice("'Nope' a Pair") { nope_pair }
272
+ menu.choice("Main Menu") { action_menu }
273
+ end
274
+ end
275
+
276
+ def save_pairs
277
+ @generator.save_pairs
278
+ puts
279
+ PairSaver.new(@generator.pairs).post_to_slack
280
+ say "Posted pairings to slack!"
281
+ @sync.save
282
+ action_menu
283
+ end
284
+
285
+ def simulate_pairs
286
+ times = ask("How many days should we to travel?")
287
+
288
+ times.to_i.times do
289
+ @generator.generate_pairs
290
+ @generator.save_pairs
291
+ end
292
+
293
+ display_pair_stats
294
+
295
+ action_menu
296
+ end
297
+
298
+ def all_combos
299
+ @matrix.people.combination(2)
300
+ end
301
+
302
+ def display_pair_stats
303
+ rows = []
304
+ all_combos.each do |person1, person2|
305
+ display_pair(rows, person1, person2)
306
+ end
307
+ table = Terminal::Table.new headings: ["Pair","Times"], rows: rows
308
+ puts table
309
+ end
310
+
311
+ def display_pairs
312
+ rows = []
313
+ @generator.pairs.each do |person1, person2|
314
+ display_pair(rows, person1, person2)
315
+ end
316
+ table = Terminal::Table.new headings: ["Pair","Times"], rows: rows
317
+ puts table
318
+ end
319
+
320
+ def display_pair_names(pair)
321
+ person1, person2 = pair
322
+ Rainbow(person1).white + " & " + Rainbow(person2).white
323
+ end
324
+
325
+ def display_pair(rows, person1, person2)
326
+ cols = []
327
+ cols << display_pair_names([person1, person2])
328
+ cols << @renderer.stats_for_pair(person1, person2)
329
+ rows << cols
330
+ end
331
+
332
+ def load_google
333
+ @sync.load
334
+ end
335
+ end
336
+ end