active_genie 0.25.0 → 0.25.2
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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/VERSION +1 -1
- data/lib/active_genie/battle/README.md +7 -7
- data/lib/active_genie/battle/generalist.json +36 -0
- data/lib/active_genie/battle/generalist.md +16 -0
- data/lib/active_genie/battle/generalist.rb +16 -69
- data/lib/active_genie/clients/providers/anthropic_client.rb +62 -39
- data/lib/active_genie/clients/providers/base_client.rb +38 -51
- data/lib/active_genie/clients/providers/deepseek_client.rb +56 -49
- data/lib/active_genie/clients/providers/google_client.rb +53 -53
- data/lib/active_genie/clients/providers/openai_client.rb +53 -54
- data/lib/active_genie/clients/unified_client.rb +6 -5
- data/lib/active_genie/config/battle_config.rb +2 -0
- data/lib/active_genie/config/data_extractor_config.rb +3 -3
- data/lib/active_genie/config/llm_config.rb +3 -1
- data/lib/active_genie/config/log_config.rb +33 -11
- data/lib/active_genie/config/providers/anthropic_config.rb +2 -2
- data/lib/active_genie/config/providers/deepseek_config.rb +2 -2
- data/lib/active_genie/config/providers/google_config.rb +2 -2
- data/lib/active_genie/config/providers/openai_config.rb +2 -2
- data/lib/active_genie/config/providers_config.rb +6 -5
- data/lib/active_genie/config/scoring_config.rb +2 -0
- data/lib/active_genie/configuration.rb +14 -6
- data/lib/active_genie/data_extractor/from_informal.json +11 -0
- data/lib/active_genie/data_extractor/from_informal.rb +3 -11
- data/lib/active_genie/data_extractor/generalist.json +9 -0
- data/lib/active_genie/data_extractor/generalist.rb +14 -13
- data/lib/active_genie/errors/invalid_log_output_error.rb +19 -0
- data/lib/active_genie/errors/invalid_provider_error.rb +8 -4
- data/lib/active_genie/logger.rb +10 -4
- data/lib/active_genie/{concerns → ranking/concerns}/loggable.rb +2 -5
- data/lib/active_genie/ranking/elo_round.rb +31 -27
- data/lib/active_genie/ranking/free_for_all.rb +29 -21
- data/lib/active_genie/ranking/player.rb +45 -17
- data/lib/active_genie/ranking/players_collection.rb +16 -6
- data/lib/active_genie/ranking/ranking.rb +21 -20
- data/lib/active_genie/ranking/ranking_scoring.rb +2 -19
- data/lib/active_genie/scoring/generalist.json +9 -0
- data/lib/active_genie/scoring/generalist.md +46 -0
- data/lib/active_genie/scoring/generalist.rb +13 -65
- data/lib/active_genie/scoring/recommended_reviewers.rb +2 -2
- metadata +11 -4
@@ -38,21 +38,19 @@ module ActiveGenie
|
|
38
38
|
@llm ||= Config::LlmConfig.new
|
39
39
|
end
|
40
40
|
|
41
|
+
SUB_CONFIGS = %w[log providers llm ranking scoring data_extractor battle].freeze
|
42
|
+
|
41
43
|
def merge(config_params = {})
|
42
44
|
return config_params if config_params.is_a?(Configuration)
|
43
45
|
|
44
46
|
new_configuration = dup
|
45
47
|
|
46
|
-
|
48
|
+
SUB_CONFIGS.each do |key|
|
47
49
|
config = new_configuration.send(key)
|
48
50
|
|
49
51
|
next unless config.respond_to?(:merge)
|
50
52
|
|
51
|
-
new_config =
|
52
|
-
config.merge(config_params[key])
|
53
|
-
else
|
54
|
-
config.merge(config_params)
|
55
|
-
end
|
53
|
+
new_config = sub_config_merge(config, key, config_params)
|
56
54
|
|
57
55
|
new_configuration.send("#{key}=", new_config)
|
58
56
|
end
|
@@ -60,6 +58,16 @@ module ActiveGenie
|
|
60
58
|
new_configuration
|
61
59
|
end
|
62
60
|
|
61
|
+
def sub_config_merge(config, key, config_params)
|
62
|
+
if config_params.key?(key.to_s)
|
63
|
+
config.merge(config_params[key.to_s])
|
64
|
+
elsif config_params.key?(key.to_sym)
|
65
|
+
config.merge(config_params[key.to_sym])
|
66
|
+
else
|
67
|
+
config.merge(config_params)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
63
71
|
attr_writer :log, :providers, :ranking, :scoring, :data_extractor, :battle, :llm
|
64
72
|
end
|
65
73
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
{
|
3
|
+
"message_litote": {
|
4
|
+
"type": "boolean",
|
5
|
+
"description": "Return true if the message is a litote. A litote is a figure of speech that uses understatement to emphasize a point by stating a negative to further affirm a positive, often incorporating double negatives for effect."
|
6
|
+
},
|
7
|
+
"litote_rephrased": {
|
8
|
+
"type": "string",
|
9
|
+
"description": "The true meaning of the litote. Rephrase the message to a positive and active statement."
|
10
|
+
}
|
11
|
+
}
|
@@ -47,17 +47,9 @@ module ActiveGenie
|
|
47
47
|
private
|
48
48
|
|
49
49
|
def data_to_extract_with_litote
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
type: 'boolean',
|
54
|
-
description: 'Return true if the message is a litote. A litote is a figure of speech that uses understatement to emphasize a point by stating a negative to further affirm a positive, often incorporating double negatives for effect.'
|
55
|
-
},
|
56
|
-
litote_rephrased: {
|
57
|
-
type: 'string',
|
58
|
-
description: 'The true meaning of the litote. Rephrase the message to a positive and active statement.'
|
59
|
-
}
|
60
|
-
}
|
50
|
+
parameters = JSON.parse(File.read(File.join(__dir__, 'from_informal.json')), symbolize_names: true)
|
51
|
+
|
52
|
+
@data_to_extract.merge(parameters)
|
61
53
|
end
|
62
54
|
end
|
63
55
|
end
|
@@ -42,15 +42,9 @@ module ActiveGenie
|
|
42
42
|
|
43
43
|
properties = data_to_extract_with_explanation
|
44
44
|
|
45
|
-
function =
|
46
|
-
|
47
|
-
|
48
|
-
parameters: {
|
49
|
-
type: 'object',
|
50
|
-
properties:,
|
51
|
-
required: properties.keys
|
52
|
-
}
|
53
|
-
}
|
45
|
+
function = JSON.parse(File.read(File.join(__dir__, 'generalist.json')), symbolize_names: true)
|
46
|
+
function[:parameters][:properties] = properties
|
47
|
+
function[:parameters][:required] = properties.keys
|
54
48
|
|
55
49
|
response = function_calling(messages, function)
|
56
50
|
|
@@ -68,11 +62,18 @@ module ActiveGenie
|
|
68
62
|
with_explanation[key] = value
|
69
63
|
with_explanation["#{key}_explanation"] = {
|
70
64
|
type: 'string',
|
71
|
-
description: "
|
65
|
+
description: "
|
66
|
+
The chain of thought that led to the conclusion about: #{key}.
|
67
|
+
Can be blank if the user didn't provide any context
|
68
|
+
"
|
72
69
|
}
|
73
70
|
with_explanation["#{key}_accuracy"] = {
|
74
71
|
type: 'integer',
|
75
|
-
description: '
|
72
|
+
description: '
|
73
|
+
The accuracy of the extracted data, what is the percentage of confidence?
|
74
|
+
When 100 it means the data is explicitly stated in the text.
|
75
|
+
When 0 it means is no way to discover the data from the text
|
76
|
+
'
|
76
77
|
}
|
77
78
|
end
|
78
79
|
|
@@ -104,10 +105,10 @@ module ActiveGenie
|
|
104
105
|
simplified_response = {}
|
105
106
|
|
106
107
|
@data_to_extract.each_key do |key|
|
107
|
-
next
|
108
|
+
next unless response.key?(key.to_s)
|
108
109
|
next if response.key?("#{key}_accuracy") && response["#{key}_accuracy"] < min_accuracy
|
109
110
|
|
110
|
-
simplified_response[key] = response[key]
|
111
|
+
simplified_response[key] = response[key.to_s]
|
111
112
|
end
|
112
113
|
|
113
114
|
simplified_response
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGenie
|
4
|
+
class InvalidLogOutputError < StandardError
|
5
|
+
TEXT = <<~TEXT
|
6
|
+
Invalid log output option. Must be a callable object. Given: %<output>s
|
7
|
+
Example:
|
8
|
+
```ruby
|
9
|
+
ActiveGenie.configure do |config|
|
10
|
+
config.log.output = ->(log) { puts log }
|
11
|
+
end
|
12
|
+
```
|
13
|
+
TEXT
|
14
|
+
|
15
|
+
def initialize(output)
|
16
|
+
super(format(TEXT, output:))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -9,8 +9,8 @@ module ActiveGenie
|
|
9
9
|
1. Set up global configuration:
|
10
10
|
```ruby
|
11
11
|
ActiveGenie.configure do |config|
|
12
|
-
config.
|
13
|
-
config.api_key = 'your_api_key'
|
12
|
+
config.providers.default = 'openai'
|
13
|
+
config.providers.openai.api_key = 'your_api_key'
|
14
14
|
# ... other configuration options
|
15
15
|
end
|
16
16
|
```
|
@@ -21,8 +21,12 @@ module ActiveGenie
|
|
21
21
|
arg1,
|
22
22
|
arg2,
|
23
23
|
config: {
|
24
|
-
|
25
|
-
|
24
|
+
providers: {
|
25
|
+
default: 'openai',
|
26
|
+
openai: {
|
27
|
+
api_key: 'your_api_key'
|
28
|
+
}
|
29
|
+
}
|
26
30
|
}
|
27
31
|
)
|
28
32
|
```
|
data/lib/active_genie/logger.rb
CHANGED
@@ -16,8 +16,7 @@ module ActiveGenie
|
|
16
16
|
}
|
17
17
|
|
18
18
|
persist!(log)
|
19
|
-
|
20
|
-
ActiveGenie.configuration.log.call_observers(log)
|
19
|
+
config.output_call(log)
|
21
20
|
|
22
21
|
log
|
23
22
|
end
|
@@ -35,8 +34,15 @@ module ActiveGenie
|
|
35
34
|
attr_accessor :context
|
36
35
|
|
37
36
|
def persist!(log)
|
38
|
-
|
39
|
-
File.
|
37
|
+
file_path = log.key?(:fine_tune) && log[:fine_tune] ? config.fine_tune_file_path : config.file_path
|
38
|
+
folder_path = File.dirname(file_path)
|
39
|
+
|
40
|
+
FileUtils.mkdir_p(folder_path)
|
41
|
+
File.write(file_path, "#{JSON.generate(log)}\n", mode: 'a')
|
42
|
+
end
|
43
|
+
|
44
|
+
def config
|
45
|
+
ActiveGenie.configuration.log
|
40
46
|
end
|
41
47
|
end
|
42
48
|
end
|
@@ -16,37 +16,30 @@ module ActiveGenie
|
|
16
16
|
@criteria = criteria
|
17
17
|
@config = config
|
18
18
|
@tmp_defenders = []
|
19
|
-
@start_time = Time.now
|
20
19
|
@total_tokens = 0
|
21
|
-
@previous_elo = {}
|
20
|
+
@previous_elo = players.to_h { |player| [player.id, player.elo] }
|
22
21
|
@previous_highest_elo = @defender_tier.max_by(&:elo).elo
|
23
22
|
end
|
24
23
|
|
25
24
|
def call
|
26
25
|
ActiveGenie::Logger.with_context(log_context) do
|
27
|
-
|
28
|
-
matches.each do |player_1, player_2|
|
26
|
+
matches.each do |player_a, player_b|
|
29
27
|
# TODO: battle can take a while, can be parallelized
|
30
|
-
winner, loser = battle(
|
31
|
-
|
32
|
-
|
33
|
-
winner.elo = calculate_new_elo(winner.elo, loser.elo, 1)
|
34
|
-
loser.elo = calculate_new_elo(loser.elo, winner.elo, 0)
|
28
|
+
winner, loser = battle(player_a, player_b)
|
29
|
+
update_players_elo(winner, loser)
|
35
30
|
end
|
36
31
|
end
|
37
32
|
|
38
|
-
|
39
|
-
|
40
|
-
report
|
33
|
+
build_report
|
41
34
|
end
|
42
35
|
|
43
|
-
private
|
44
|
-
|
45
36
|
BATTLE_PER_PLAYER = 3
|
46
37
|
K = 32
|
47
38
|
|
39
|
+
private
|
40
|
+
|
48
41
|
def save_previous_elo
|
49
|
-
@previous_elo = @players.
|
42
|
+
@previous_elo = @players.to_h { |player| [player.id, player.elo] }
|
50
43
|
end
|
51
44
|
|
52
45
|
def matches
|
@@ -63,18 +56,18 @@ module ActiveGenie
|
|
63
56
|
@tmp_defenders.pop
|
64
57
|
end
|
65
58
|
|
66
|
-
def battle(
|
67
|
-
ActiveGenie::Logger.with_context({
|
59
|
+
def battle(player_a, player_b)
|
60
|
+
ActiveGenie::Logger.with_context({ player_a_id: player_a.id, player_b_id: player_b.id }) do
|
68
61
|
result = ActiveGenie::Battle.call(
|
69
|
-
|
70
|
-
|
62
|
+
player_a.content,
|
63
|
+
player_b.content,
|
71
64
|
@criteria,
|
72
65
|
config: @config
|
73
66
|
)
|
74
67
|
|
75
68
|
winner, loser = case result['winner']
|
76
|
-
when '
|
77
|
-
when '
|
69
|
+
when 'player_a' then [player_a, player_b]
|
70
|
+
when 'player_b' then [player_b, player_a]
|
78
71
|
when 'draw' then [nil, nil]
|
79
72
|
end
|
80
73
|
|
@@ -82,9 +75,16 @@ module ActiveGenie
|
|
82
75
|
end
|
83
76
|
end
|
84
77
|
|
78
|
+
def update_players_elo(winner, loser)
|
79
|
+
return if winner.nil? || loser.nil?
|
80
|
+
|
81
|
+
winner.elo = calculate_new_elo(winner.elo, loser.elo, 1)
|
82
|
+
loser.elo = calculate_new_elo(loser.elo, winner.elo, 0)
|
83
|
+
end
|
84
|
+
|
85
85
|
# INFO: Read more about the Elo rating system on https://en.wikipedia.org/wiki/Elo_rating_system
|
86
86
|
def calculate_new_elo(player_rating, opponent_rating, score)
|
87
|
-
expected_score = 1.0 / (1.0 + 10.0**((opponent_rating - player_rating) / 400.0))
|
87
|
+
expected_score = 1.0 / (1.0 + (10.0**((opponent_rating - player_rating) / 400.0)))
|
88
88
|
|
89
89
|
player_rating + (K * (score - expected_score)).round
|
90
90
|
end
|
@@ -101,18 +101,21 @@ module ActiveGenie
|
|
101
101
|
Digest::MD5.hexdigest(ranking_unique_key)
|
102
102
|
end
|
103
103
|
|
104
|
-
def
|
105
|
-
{
|
104
|
+
def build_report
|
105
|
+
report = {
|
106
106
|
elo_round_id:,
|
107
107
|
players_in_round: players_in_round.map(&:id),
|
108
108
|
battles_count: matches.size,
|
109
|
-
duration_seconds: Time.now - @start_time,
|
110
109
|
total_tokens: @total_tokens,
|
111
110
|
previous_highest_elo: @previous_highest_elo,
|
112
111
|
highest_elo:,
|
113
112
|
highest_elo_diff: highest_elo - @previous_highest_elo,
|
114
113
|
players_elo_diff:
|
115
114
|
}
|
115
|
+
|
116
|
+
ActiveGenie::Logger.call({ code: :elo_round_report, **report })
|
117
|
+
|
118
|
+
report
|
116
119
|
end
|
117
120
|
|
118
121
|
def players_in_round
|
@@ -124,9 +127,10 @@ module ActiveGenie
|
|
124
127
|
end
|
125
128
|
|
126
129
|
def players_elo_diff
|
127
|
-
players_in_round.map do |player|
|
130
|
+
elo_diffs = players_in_round.map do |player|
|
128
131
|
[player.id, player.elo - @previous_elo[player.id]]
|
129
|
-
end
|
132
|
+
end
|
133
|
+
elo_diffs.sort_by { |_, diff| -diff }.to_h
|
130
134
|
end
|
131
135
|
|
132
136
|
def log_observer(log)
|
@@ -19,22 +19,14 @@ module ActiveGenie
|
|
19
19
|
|
20
20
|
def call
|
21
21
|
ActiveGenie::Logger.with_context(log_context, observer: method(:log_observer)) do
|
22
|
-
matches.each do |
|
23
|
-
winner, loser = battle(
|
24
|
-
|
25
|
-
|
26
|
-
player_1.draw!
|
27
|
-
player_2.draw!
|
28
|
-
else
|
29
|
-
winner.win!
|
30
|
-
loser.lose!
|
31
|
-
end
|
22
|
+
matches.each do |player_a, player_b|
|
23
|
+
winner, loser = battle(player_a, player_b)
|
24
|
+
|
25
|
+
update_players_score(winner, loser)
|
32
26
|
end
|
33
27
|
end
|
34
28
|
|
35
|
-
|
36
|
-
|
37
|
-
report
|
29
|
+
build_report
|
38
30
|
end
|
39
31
|
|
40
32
|
private
|
@@ -45,23 +37,23 @@ module ActiveGenie
|
|
45
37
|
@players.eligible.combination(2).to_a
|
46
38
|
end
|
47
39
|
|
48
|
-
def battle(
|
40
|
+
def battle(player_a, player_b)
|
49
41
|
result = ActiveGenie::Battle.call(
|
50
|
-
|
51
|
-
|
42
|
+
player_a.content,
|
43
|
+
player_b.content,
|
52
44
|
@criteria,
|
53
45
|
config: @config
|
54
46
|
)
|
55
47
|
|
56
48
|
winner, loser = case result['winner']
|
57
|
-
when '
|
58
|
-
when '
|
49
|
+
when 'player_a' then [player_a, player_b, result['reasoning']]
|
50
|
+
when 'player_b' then [player_b, player_a, result['reasoning']]
|
59
51
|
when 'draw' then [nil, nil, result['reasoning']]
|
60
52
|
end
|
61
53
|
|
62
54
|
ActiveGenie::Logger.call({
|
63
55
|
code: :free_for_all_battle,
|
64
|
-
player_ids: [
|
56
|
+
player_ids: [player_a.id, player_b.id],
|
65
57
|
winner_id: winner&.id,
|
66
58
|
loser_id: loser&.id,
|
67
59
|
reasoning: result['reasoning']
|
@@ -70,6 +62,18 @@ module ActiveGenie
|
|
70
62
|
[winner, loser]
|
71
63
|
end
|
72
64
|
|
65
|
+
def update_players_score(winner, loser)
|
66
|
+
return if winner.nil? || loser.nil?
|
67
|
+
|
68
|
+
if winner.nil? || loser.nil?
|
69
|
+
player_a.draw!
|
70
|
+
player_b.draw!
|
71
|
+
else
|
72
|
+
winner.win!
|
73
|
+
loser.lose!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
73
77
|
def log_context
|
74
78
|
{ free_for_all_id: }
|
75
79
|
end
|
@@ -80,13 +84,17 @@ module ActiveGenie
|
|
80
84
|
Digest::MD5.hexdigest(ranking_unique_key)
|
81
85
|
end
|
82
86
|
|
83
|
-
def
|
84
|
-
{
|
87
|
+
def build_report
|
88
|
+
report = {
|
85
89
|
free_for_all_id:,
|
86
90
|
battles_count: matches.size,
|
87
91
|
duration_seconds: Time.now - @start_time,
|
88
92
|
total_tokens: @total_tokens
|
89
93
|
}
|
94
|
+
|
95
|
+
ActiveGenie::Logger.call({ code: :free_for_all_report, **report })
|
96
|
+
|
97
|
+
report
|
90
98
|
end
|
91
99
|
|
92
100
|
def log_observer(log)
|
@@ -6,20 +6,48 @@ module ActiveGenie
|
|
6
6
|
module Ranking
|
7
7
|
class Player
|
8
8
|
def initialize(params)
|
9
|
-
params = { content: params }
|
10
|
-
|
11
|
-
@content = params[:content] || params
|
12
|
-
@name = params[:name] || params[:content][0..10]
|
13
|
-
@id = params[:id] || Digest::MD5.hexdigest(@content)
|
14
|
-
@score = params[:score] || nil
|
15
|
-
@elo = params[:elo] || nil
|
16
|
-
@ffa_win_count = params[:ffa_win_count] || 0
|
17
|
-
@ffa_lose_count = params[:ffa_lose_count] || 0
|
18
|
-
@ffa_draw_count = params[:ffa_draw_count] || 0
|
19
|
-
@eliminated = params[:eliminated] || nil
|
9
|
+
@params = params.is_a?(String) ? { content: params } : params.dup
|
10
|
+
@params[:content] ||= @params
|
20
11
|
end
|
12
|
+
|
21
13
|
attr_accessor :rank
|
22
14
|
|
15
|
+
def content
|
16
|
+
@content ||= @params[:content]
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
@name ||= @params[:name] || content[0..10]
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
@id ||= @params[:id] || Digest::MD5.hexdigest(content.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def score
|
28
|
+
@score ||= @params[:score]
|
29
|
+
end
|
30
|
+
|
31
|
+
def elo
|
32
|
+
@elo ||= @params[:elo]
|
33
|
+
end
|
34
|
+
|
35
|
+
def ffa_win_count
|
36
|
+
@ffa_win_count ||= @params[:ffa_win_count] || 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def ffa_lose_count
|
40
|
+
@ffa_lose_count ||= @params[:ffa_lose_count] || 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def ffa_draw_count
|
44
|
+
@ffa_draw_count ||= @params[:ffa_draw_count] || 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def eliminated
|
48
|
+
@eliminated ||= @params[:eliminated]
|
49
|
+
end
|
50
|
+
|
23
51
|
def score=(value)
|
24
52
|
ActiveGenie::Logger.call({ code: :new_score, player_id: id, score: value }) if value != @score
|
25
53
|
@score = value
|
@@ -51,10 +79,8 @@ module ActiveGenie
|
|
51
79
|
ActiveGenie::Logger.call({ code: :new_ffa_score, player_id: id, result: 'lose', ffa_score: })
|
52
80
|
end
|
53
81
|
|
54
|
-
attr_reader :id, :content, :score, :elo, :ffa_win_count, :ffa_lose_count, :ffa_draw_count, :eliminated, :name
|
55
|
-
|
56
82
|
def ffa_score
|
57
|
-
@ffa_win_count * 3 + @ffa_draw_count
|
83
|
+
(@ffa_win_count * 3) + @ffa_draw_count
|
58
84
|
end
|
59
85
|
|
60
86
|
def sort_value
|
@@ -73,7 +99,7 @@ module ActiveGenie
|
|
73
99
|
}
|
74
100
|
end
|
75
101
|
|
76
|
-
def method_missing(method_name, *args, &
|
102
|
+
def method_missing(method_name, *args, &)
|
77
103
|
if method_name == :[] && args.size == 1
|
78
104
|
attr_name = args.first.to_sym
|
79
105
|
|
@@ -90,11 +116,13 @@ module ActiveGenie
|
|
90
116
|
method_name == :[] || super
|
91
117
|
end
|
92
118
|
|
119
|
+
BASE_ELO = 1000
|
120
|
+
|
121
|
+
private
|
122
|
+
|
93
123
|
def generate_elo_by_score
|
94
124
|
BASE_ELO + ((@score || 0) - 50)
|
95
125
|
end
|
96
|
-
|
97
|
-
BASE_ELO = 1000
|
98
126
|
end
|
99
127
|
end
|
100
128
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'player'
|
4
4
|
|
5
5
|
module ActiveGenie
|
6
6
|
module Ranking
|
@@ -11,18 +11,26 @@ module ActiveGenie
|
|
11
11
|
attr_reader :players
|
12
12
|
|
13
13
|
def coefficient_of_variation
|
14
|
-
|
15
|
-
return nil if score_list.empty?
|
14
|
+
mean = score_mean
|
16
15
|
|
17
|
-
mean = score_list.sum.to_f / score_list.size
|
18
16
|
return nil if mean.zero?
|
19
17
|
|
20
|
-
variance =
|
18
|
+
variance = all_scores.map { |num| (num - mean)**2 }.sum / all_scores.size
|
21
19
|
standard_deviation = Math.sqrt(variance)
|
22
20
|
|
23
21
|
(standard_deviation / mean) * 100
|
24
22
|
end
|
25
23
|
|
24
|
+
def all_scores
|
25
|
+
eligible.map(&:score).compact
|
26
|
+
end
|
27
|
+
|
28
|
+
def score_mean
|
29
|
+
return 0 if all_scores.empty?
|
30
|
+
|
31
|
+
all_scores.sum.to_f / all_scores.size
|
32
|
+
end
|
33
|
+
|
26
34
|
def calc_relegation_tier
|
27
35
|
eligible[(tier_size * -1)..]
|
28
36
|
end
|
@@ -80,7 +88,9 @@ module ActiveGenie
|
|
80
88
|
# - 14 eligible, tier_size: 4
|
81
89
|
# 4 rounds to reach top 10 with 50 players
|
82
90
|
def tier_size
|
83
|
-
|
91
|
+
size = (eligible_size / 3).ceil
|
92
|
+
|
93
|
+
size.clamp(10, eligible_size - 10)
|
84
94
|
end
|
85
95
|
end
|
86
96
|
end
|