active_genie 0.0.12 → 0.0.18

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -22
  3. data/VERSION +1 -1
  4. data/lib/active_genie/battle/README.md +7 -7
  5. data/lib/active_genie/battle/basic.rb +47 -31
  6. data/lib/active_genie/battle.rb +4 -0
  7. data/lib/active_genie/clients/anthropic_client.rb +110 -0
  8. data/lib/active_genie/clients/google_client.rb +158 -0
  9. data/lib/active_genie/clients/helpers/retry.rb +29 -0
  10. data/lib/active_genie/clients/openai_client.rb +61 -83
  11. data/lib/active_genie/clients/unified_client.rb +4 -4
  12. data/lib/active_genie/concerns/loggable.rb +44 -0
  13. data/lib/active_genie/configuration/log_config.rb +1 -1
  14. data/lib/active_genie/configuration/providers/anthropic_config.rb +54 -0
  15. data/lib/active_genie/configuration/providers/base_config.rb +85 -0
  16. data/lib/active_genie/configuration/providers/deepseek_config.rb +54 -0
  17. data/lib/active_genie/configuration/providers/google_config.rb +56 -0
  18. data/lib/active_genie/configuration/providers/openai_config.rb +54 -0
  19. data/lib/active_genie/configuration/providers_config.rb +7 -4
  20. data/lib/active_genie/configuration/runtime_config.rb +35 -0
  21. data/lib/active_genie/configuration.rb +18 -4
  22. data/lib/active_genie/data_extractor/basic.rb +15 -2
  23. data/lib/active_genie/data_extractor.rb +4 -0
  24. data/lib/active_genie/logger.rb +40 -21
  25. data/lib/active_genie/ranking/elo_round.rb +71 -50
  26. data/lib/active_genie/ranking/free_for_all.rb +31 -14
  27. data/lib/active_genie/ranking/player.rb +11 -16
  28. data/lib/active_genie/ranking/players_collection.rb +4 -4
  29. data/lib/active_genie/ranking/ranking.rb +74 -19
  30. data/lib/active_genie/ranking/ranking_scoring.rb +3 -3
  31. data/lib/active_genie/scoring/basic.rb +44 -25
  32. data/lib/active_genie/scoring.rb +3 -0
  33. data/lib/tasks/benchmark.rake +27 -0
  34. metadata +91 -70
  35. data/lib/active_genie/configuration/openai_config.rb +0 -56
@@ -1,6 +1,10 @@
1
1
  require_relative 'configuration/providers_config'
2
- require_relative 'configuration/openai_config'
2
+ require_relative 'configuration/providers/openai_config'
3
+ require_relative 'configuration/providers/google_config'
4
+ require_relative 'configuration/providers/anthropic_config'
5
+ require_relative 'configuration/providers/deepseek_config'
3
6
  require_relative 'configuration/log_config'
7
+ require_relative 'configuration/runtime_config'
4
8
 
5
9
  module ActiveGenie
6
10
  module Configuration
@@ -9,7 +13,10 @@ module ActiveGenie
9
13
  def providers
10
14
  @providers ||= begin
11
15
  p = ProvidersConfig.new
12
- p.register(:openai, ActiveGenie::Configuration::OpenaiConfig)
16
+ p.register(ActiveGenie::Configuration::Providers::OpenaiConfig)
17
+ p.register(ActiveGenie::Configuration::Providers::GoogleConfig)
18
+ p.register(ActiveGenie::Configuration::Providers::AnthropicConfig)
19
+ p.register(ActiveGenie::Configuration::Providers::DeepseekConfig)
13
20
  p
14
21
  end
15
22
  end
@@ -18,10 +25,17 @@ module ActiveGenie
18
25
  @log ||= LogConfig.new
19
26
  end
20
27
 
28
+ def runtime
29
+ @runtime ||= RuntimeConfig.new
30
+ end
31
+
21
32
  def to_h(configs = {})
33
+ normalized_configs = configs.dig(:runtime) ? configs : { runtime: configs }
34
+
22
35
  {
23
- providers: providers.to_h(configs.dig(:providers) || {}),
24
- log: log.to_h(configs.dig(:log) || {})
36
+ providers: providers.to_h(normalized_configs.dig(:providers) || {}),
37
+ log: log.to_h(normalized_configs.dig(:log) || {}),
38
+ runtime: runtime.to_h(normalized_configs.dig(:runtime) || {})
25
39
  }
26
40
  end
27
41
  end
@@ -37,21 +37,34 @@ module ActiveGenie::DataExtractor
37
37
  { role: 'system', content: PROMPT },
38
38
  { role: 'user', content: @text }
39
39
  ]
40
+
41
+ properties = data_to_extract_with_explaination
42
+
40
43
  function = {
41
44
  name: 'data_extractor',
42
45
  description: 'Extract structured and typed data from user messages.',
43
46
  schema: {
44
47
  type: "object",
45
- properties: data_to_extract_with_explaination
48
+ properties:,
49
+ required: properties.keys
46
50
  }
47
51
  }
48
52
 
49
- ::ActiveGenie::Clients::UnifiedClient.function_calling(
53
+ result = ::ActiveGenie::Clients::UnifiedClient.function_calling(
50
54
  messages,
51
55
  function,
52
56
  model_tier: 'lower_tier',
53
57
  config: @config
54
58
  )
59
+
60
+ ActiveGenie::Logger.debug({
61
+ code: :data_extractor,
62
+ text: @text[0..30],
63
+ data_to_extract: @data_to_extract,
64
+ extracted_data: result
65
+ })
66
+
67
+ result
55
68
  end
56
69
 
57
70
  private
@@ -10,6 +10,10 @@ module ActiveGenie
10
10
  Basic.call(...)
11
11
  end
12
12
 
13
+ def call(...)
14
+ Basic.call(...)
15
+ end
16
+
13
17
  def from_informal(...)
14
18
  FromInformal.call(...)
15
19
  end
@@ -5,56 +5,55 @@ module ActiveGenie
5
5
  module Logger
6
6
  module_function
7
7
 
8
- def with_context(context)
8
+ def with_context(context, observer: nil)
9
9
  @context ||= {}
10
+ @observers ||= []
10
11
  begin
11
12
  @context = @context.merge(context)
13
+ @observers << observer if observer
12
14
  yield if block_given?
13
15
  ensure
14
16
  @context.delete_if { |key, _| context.key?(key) }
17
+ @observers.delete(observer)
15
18
  end
16
19
  end
17
20
 
18
21
  def info(log)
19
- save(log, level: :info)
22
+ call(log, level: :info)
20
23
  end
21
24
 
22
25
  def error(log)
23
- save(log, level: :error)
26
+ call(log, level: :error)
24
27
  end
25
28
 
26
29
  def warn(log)
27
- save(log, level: :warn)
30
+ call(log, level: :warn)
28
31
  end
29
32
 
30
33
  def debug(log)
31
- save(log, level: :debug)
34
+ call(log, level: :debug)
32
35
  end
33
36
 
34
37
  def trace(log)
35
- save(log, level: :trace)
38
+ call(log, level: :trace)
36
39
  end
37
40
 
38
- def save(data, level: :info)
39
- log = @context.merge(data || {})
40
- log[:timestamp] = Time.now
41
- log[:level] = level.to_s.upcase
42
- log[:process_id] = Process.pid
43
- config_log_level = LOG_LEVELS[log.dig(:config, :log_level)] || LOG_LEVELS[:info]
41
+ def call(data, level: :info)
42
+ log = {
43
+ **(@context || {}),
44
+ **(data || {}),
45
+ timestamp: Time.now,
46
+ level: level.to_s.upcase,
47
+ process_id: Process.pid
48
+ }
44
49
 
45
- FileUtils.mkdir_p('logs')
46
- File.write('logs/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
47
- if config_log_level >= LOG_LEVELS[level]
48
- $stdout.puts log
49
- else
50
- $stdout.print '.'
51
- end
50
+ append_to_file(log)
51
+ output(log, level)
52
+ call_observers(log)
52
53
 
53
54
  log
54
55
  end
55
56
 
56
- private
57
-
58
57
  # Log Levels
59
58
  #
60
59
  # LOG_LEVELS defines different levels of logging within the application.
@@ -68,5 +67,25 @@ module ActiveGenie
68
67
  LOG_LEVELS = { info: 0, error: 0, warn: 1, debug: 2, trace: 3 }.freeze
69
68
 
70
69
  attr_accessor :context
70
+
71
+ def append_to_file(log)
72
+ FileUtils.mkdir_p('logs')
73
+ File.write('logs/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
74
+ end
75
+
76
+ def output(log, level)
77
+ config_log_level = LOG_LEVELS[log.dig(:config, :log_level)] || LOG_LEVELS[:info]
78
+ if config_log_level >= LOG_LEVELS[level]
79
+ $stdout.puts log
80
+ else
81
+ $stdout.print '.'
82
+ end
83
+ end
84
+
85
+ def call_observers(log)
86
+ return if @observers.nil? || @observers.size.zero?
87
+
88
+ @observers.each { |observer| observer.call(log) }
89
+ end
71
90
  end
72
91
  end
@@ -13,32 +13,39 @@ module ActiveGenie::Ranking
13
13
  @criteria = criteria
14
14
  @config = config
15
15
  @tmp_defenders = []
16
+ @start_time = Time.now
17
+ @total_tokens = 0
18
+ @previous_elo = {}
19
+ @previous_highest_elo = @defender_tier.max_by(&:elo).elo
16
20
  end
17
21
 
18
22
  def call
19
23
  ActiveGenie::Logger.with_context(log_context) do
20
- matches.each do |player_a, player_b|
24
+ save_previous_elo
25
+ matches.each do |player_1, player_2|
21
26
  # TODO: battle can take a while, can be parallelized
22
- winner, loser = battle(player_a, player_b)
23
-
27
+ winner, loser = battle(player_1, player_2)
24
28
  next if winner.nil? || loser.nil?
25
29
 
26
- new_winner_elo, new_loser_elo = calculate_new_elo(winner.elo, loser.elo)
27
-
28
- winner.elo = new_winner_elo
29
- loser.elo = new_loser_elo
30
+ winner.elo = calculate_new_elo(winner.elo, loser.elo, 1)
31
+ loser.elo = calculate_new_elo(loser.elo, winner.elo, 0)
30
32
  end
31
-
32
- # TODO: add a round report. Duration, Elo changes, etc.
33
33
  end
34
+
35
+ ActiveGenie::Logger.info({ code: :elo_round_report, **report })
36
+
37
+ report
34
38
  end
35
39
 
36
40
  private
37
41
 
38
42
  BATTLE_PER_PLAYER = 3
39
- LOSE_PENALTY = 15
40
43
  K = 32
41
44
 
45
+ def save_previous_elo
46
+ @previous_elo = @players.map { |player| [player.id, player.elo] }.to_h
47
+ end
48
+
42
49
  def matches
43
50
  @relegation_tier.reduce([]) do |matches, attack_player|
44
51
  BATTLE_PER_PLAYER.times do
@@ -49,53 +56,35 @@ module ActiveGenie::Ranking
49
56
  end
50
57
 
51
58
  def next_defense_player
52
- @tmp_defenders = @defender_tier if @tmp_defenders.size.zero?
59
+ @tmp_defenders = @defender_tier.shuffle if @tmp_defenders.size.zero?
53
60
 
54
- @tmp_defenders.shuffle.pop!
61
+ @tmp_defenders.pop
55
62
  end
56
63
 
57
- def battle(player_a, player_b)
58
- result = ActiveGenie::Battle.basic(
59
- player_a,
60
- player_b,
61
- @criteria,
62
- config: @config
63
- )
64
-
65
- winner, loser = case result['winner']
66
- when 'player_a' then [player_a, player_b]
67
- when 'player_b' then [player_b, player_a]
68
- when 'draw' then [nil, nil]
69
- end
70
-
71
- ActiveGenie::Logger.debug({
72
- step: :elo_round_battle,
73
- player_ids: [player_a.id, player_b.id],
74
- winner_id: winner&.id,
75
- loser_id: loser&.id,
76
- reasoning: result['reasoning']
77
- })
64
+ def battle(player_1, player_2)
65
+ ActiveGenie::Logger.with_context({ player_1_id: player_1.id, player_2_id: player_2.id }) do
66
+ result = ActiveGenie::Battle.basic(
67
+ player_1.content,
68
+ player_2.content,
69
+ @criteria,
70
+ config: @config
71
+ )
72
+
73
+ winner, loser = case result['winner']
74
+ when 'player_1' then [player_1, player_2]
75
+ when 'player_2' then [player_2, player_1]
76
+ when 'draw' then [nil, nil]
77
+ end
78
78
 
79
- [winner, loser]
79
+ [winner, loser]
80
+ end
80
81
  end
81
82
 
82
83
  # INFO: Read more about the Elo rating system on https://en.wikipedia.org/wiki/Elo_rating_system
83
- def calculate_new_elo(winner_elo, loser_elo)
84
- expected_score_a = 1 / (1 + 10**((loser_elo - winner_elo) / 400))
85
- expected_score_b = 1 - expected_score_a
86
-
87
- new_winner_elo = [winner_elo + K * (1 - expected_score_a), max_defense_elo].min
88
- new_loser_elo = [loser_elo + K * (1 - expected_score_b) - LOSE_PENALTY, min_relegation_elo].max
89
-
90
- [new_winner_elo, new_loser_elo]
91
- end
92
-
93
- def max_defense_elo
94
- @defender_tier.max_by(&:elo).elo
95
- end
96
-
97
- def min_relegation_elo
98
- @relegation_tier.min_by(&:elo).elo
84
+ def calculate_new_elo(player_rating, opponent_rating, score)
85
+ expected_score = 1.0 / (1.0 + 10.0 ** ((opponent_rating - player_rating) / 400.0))
86
+
87
+ player_rating + (K * (score - expected_score)).round
99
88
  end
100
89
 
101
90
  def log_context
@@ -109,5 +98,37 @@ module ActiveGenie::Ranking
109
98
  ranking_unique_key = [relegation_tier_ids, defender_tier_ids, @criteria, @config.to_json].join('-')
110
99
  Digest::MD5.hexdigest(ranking_unique_key)
111
100
  end
101
+
102
+ def report
103
+ {
104
+ elo_round_id:,
105
+ players_in_round: players_in_round.map(&:id),
106
+ battles_count: matches.size,
107
+ duration_seconds: Time.now - @start_time,
108
+ total_tokens: @total_tokens,
109
+ previous_highest_elo: @previous_highest_elo,
110
+ highest_elo:,
111
+ highest_elo_diff: highest_elo - @previous_highest_elo,
112
+ players_elo_diff:,
113
+ }
114
+ end
115
+
116
+ def players_in_round
117
+ @defender_tier + @relegation_tier
118
+ end
119
+
120
+ def highest_elo
121
+ players_in_round.max_by(&:elo).elo
122
+ end
123
+
124
+ def players_elo_diff
125
+ players_in_round.map do |player|
126
+ [player.id, player.elo - @previous_elo[player.id]]
127
+ end.sort_by { |_, diff| -diff }.to_h
128
+ end
129
+
130
+ def log_observer(log)
131
+ @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
132
+ end
112
133
  end
113
134
  end
@@ -10,24 +10,28 @@ module ActiveGenie::Ranking
10
10
  @players = players
11
11
  @criteria = criteria
12
12
  @config = config
13
+ @start_time = Time.now
14
+ @total_tokens = 0
13
15
  end
14
16
 
15
17
  def call
16
- ActiveGenie::Logger.with_context(log_context) do
17
- matches.each do |player_a, player_b|
18
- winner, loser = battle(player_a, player_b)
18
+ ActiveGenie::Logger.with_context(log_context, observer: method(:log_observer)) do
19
+ matches.each do |player_1, player_2|
20
+ winner, loser = battle(player_1, player_2)
19
21
 
20
22
  if winner.nil? || loser.nil?
21
- player_a.draw!
22
- player_b.draw!
23
+ player_1.draw!
24
+ player_2.draw!
23
25
  else
24
26
  winner.win!
25
27
  loser.lose!
26
28
  end
27
29
  end
28
-
29
- # TODO: add a freeForAll report. Duration, Elo changes, etc.
30
30
  end
31
+
32
+ ActiveGenie::Logger.info({ code: :free_for_all_report, **report })
33
+
34
+ report
31
35
  end
32
36
 
33
37
  private
@@ -38,23 +42,23 @@ module ActiveGenie::Ranking
38
42
  @players.eligible.combination(2).to_a
39
43
  end
40
44
 
41
- def battle(player_a, player_b)
45
+ def battle(player_1, player_2)
42
46
  result = ActiveGenie::Battle.basic(
43
- player_a,
44
- player_b,
47
+ player_1.content,
48
+ player_2.content,
45
49
  @criteria,
46
50
  config: @config
47
51
  )
48
52
 
49
53
  winner, loser = case result['winner']
50
- when 'player_a' then [player_a, player_b, result['reasoning']]
51
- when 'player_b' then [player_b, player_a, result['reasoning']]
54
+ when 'player_1' then [player_1, player_2, result['reasoning']]
55
+ when 'player_2' then [player_2, player_1, result['reasoning']]
52
56
  when 'draw' then [nil, nil, result['reasoning']]
53
57
  end
54
58
 
55
59
  ActiveGenie::Logger.debug({
56
- step: :free_for_all_battle,
57
- player_ids: [player_a.id, player_b.id],
60
+ code: :free_for_all_battle,
61
+ player_ids: [player_1.id, player_2.id],
58
62
  winner_id: winner&.id,
59
63
  loser_id: loser&.id,
60
64
  reasoning: result['reasoning']
@@ -72,5 +76,18 @@ module ActiveGenie::Ranking
72
76
  ranking_unique_key = [eligible_ids, @criteria, @config.to_json].join('-')
73
77
  Digest::MD5.hexdigest(ranking_unique_key)
74
78
  end
79
+
80
+ def report
81
+ {
82
+ free_for_all_id:,
83
+ battles_count: matches.size,
84
+ duration_seconds: Time.now - @start_time,
85
+ total_tokens: @total_tokens,
86
+ }
87
+ end
88
+
89
+ def log_observer(log)
90
+ @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
91
+ end
75
92
  end
76
93
  end
@@ -21,39 +21,34 @@ module ActiveGenie::Ranking
21
21
  attr_accessor :rank
22
22
 
23
23
  def score=(value)
24
+ ActiveGenie::Logger.debug({ code: :new_score, player_id: id, score: value }) if value != @score
24
25
  @score = value
25
- ActiveGenie::Logger.debug({ step: :new_score, player_id: id, score: value })
26
- end
27
-
28
- def elo
29
- generate_elo_by_score if @elo.nil?
30
-
31
- @elo
26
+ @elo = generate_elo_by_score
32
27
  end
33
28
 
34
29
  def elo=(value)
30
+ ActiveGenie::Logger.debug({ code: :new_elo, player_id: id, elo: value }) if value != @elo
35
31
  @elo = value
36
- ActiveGenie::Logger.debug({ step: :new_elo, player_id: id, elo: value })
37
32
  end
38
33
 
39
34
  def eliminated=(value)
35
+ ActiveGenie::Logger.debug({ code: :new_eliminated, player_id: id, eliminated: value }) if value != @eliminated
40
36
  @eliminated = value
41
- ActiveGenie::Logger.debug({ step: :new_eliminated, player_id: id, eliminated: value })
42
37
  end
43
38
 
44
39
  def draw!
45
40
  @ffa_draw_count += 1
46
- ActiveGenie::Logger.debug({ step: :new_ffa_score, player_id: id, result: 'draw', ffa_score: })
41
+ ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'draw', ffa_score: })
47
42
  end
48
43
 
49
44
  def win!
50
45
  @ffa_win_count += 1
51
- ActiveGenie::Logger.debug({ step: :new_ffa_score, player_id: id, result: 'win', ffa_score: })
46
+ ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'win', ffa_score: })
52
47
  end
53
48
 
54
49
  def lose!
55
50
  @ffa_lose_count += 1
56
- ActiveGenie::Logger.debug({ step: :new_ffa_score, player_id: id, result: 'lose', ffa_score: })
51
+ ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'lose', ffa_score: })
57
52
  end
58
53
 
59
54
  def ffa_score
@@ -86,12 +81,12 @@ module ActiveGenie::Ranking
86
81
  method_name == :[] || super
87
82
  end
88
83
 
84
+ def generate_elo_by_score
85
+ BASE_ELO + ((@score || 0) - 50)
86
+ end
87
+
89
88
  private
90
89
 
91
90
  BASE_ELO = 1000
92
-
93
- def generate_elo_by_score
94
- @elo = BASE_ELO + ((@score || 0) - 50)
95
- end
96
91
  end
97
92
  end
@@ -41,9 +41,9 @@ module ActiveGenie::Ranking
41
41
  end
42
42
 
43
43
  def sorted
44
- @players.sort_by { |p| [-p.ffa_score, -(p.elo || 0), -(p.score || 0)] }
45
- @players.each_with_index { |p, i| p.rank = i + 1 }
46
- @players
44
+ sorted_players = @players.sort_by { |p| [-p.ffa_score, -(p.elo || 0), -(p.score || 0)] }
45
+ sorted_players.each_with_index { |p, i| p.rank = i + 1 }
46
+ sorted_players
47
47
  end
48
48
 
49
49
  def to_h
@@ -57,7 +57,7 @@ module ActiveGenie::Ranking
57
57
  private
58
58
 
59
59
  def build(param_players)
60
- param_players.map { |player| Player.new(player) }
60
+ param_players.map { |p| Player.new(p) }
61
61
  end
62
62
 
63
63
  # Returns the number of players to battle in each round
@@ -1,3 +1,4 @@
1
+ require_relative '../concerns/loggable'
1
2
  require_relative './players_collection'
2
3
  require_relative './free_for_all'
3
4
  require_relative './elo_round'
@@ -28,39 +29,55 @@ require_relative './ranking_scoring'
28
29
  # @return [Hash] Final ranked player results
29
30
  module ActiveGenie::Ranking
30
31
  class Ranking
32
+ include ActiveGenie::Concerns::Loggable
33
+
31
34
  def self.call(...)
32
35
  new(...).call
33
36
  end
34
37
 
35
38
  def initialize(param_players, criteria, reviewers: [], config: {})
36
- @param_players = param_players
37
39
  @criteria = criteria
38
40
  @reviewers = Array(reviewers).compact.uniq
39
41
  @config = ActiveGenie::Configuration.to_h(config)
40
- @players = nil
42
+ @players = PlayersCollection.new(param_players)
43
+ @elo_rounds_played = 0
44
+ @elo_round_battle_count = 0
45
+ @free_for_all_battle_count = 0
46
+ @total_tokens = 0
47
+ @start_time = Time.now
41
48
  end
42
49
 
43
50
  def call
44
- @players = PlayersCollection.new(@param_players)
45
-
46
- ActiveGenie::Logger.with_context(log_context) do
47
- set_initial_player_scores!
48
- eliminate_obvious_bad_players!
49
-
50
- while @players.elo_eligible?
51
- run_elo_round!
52
- eliminate_relegation_players!
53
- end
54
-
55
- run_free_for_all!
51
+ initial_log
52
+
53
+ set_initial_player_scores!
54
+ eliminate_obvious_bad_players!
55
+
56
+ while @players.elo_eligible?
57
+ elo_report = run_elo_round!
58
+ eliminate_relegation_players!
59
+ rebalance_players!(elo_report)
56
60
  end
57
61
 
62
+ run_free_for_all!
63
+ final_logs
64
+
58
65
  @players.sorted
59
66
  end
60
67
 
61
68
  private
62
69
 
63
- SCORE_VARIATION_THRESHOLD = 10
70
+ SCORE_VARIATION_THRESHOLD = 15
71
+ ELIMINATION_VARIATION = 'variation_too_high'
72
+ ELIMINATION_RELEGATION = 'relegation_tier'
73
+
74
+ with_logging_context :log_context, ->(log) {
75
+ @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
76
+ }
77
+
78
+ def initial_log
79
+ @players.each { |p| ActiveGenie::Logger.debug({ code: :new_player, player: p.to_h }) }
80
+ end
64
81
 
65
82
  def set_initial_player_scores!
66
83
  RankingScoring.call(@players, @criteria, reviewers: @reviewers, config: @config)
@@ -68,20 +85,58 @@ module ActiveGenie::Ranking
68
85
 
69
86
  def eliminate_obvious_bad_players!
70
87
  while @players.coefficient_of_variation >= SCORE_VARIATION_THRESHOLD
71
- @players.eligible.last.eliminated = 'variation_too_high'
88
+ @players.eligible.last.eliminated = ELIMINATION_VARIATION
72
89
  end
73
90
  end
74
91
 
75
92
  def run_elo_round!
76
- EloRound.call(@players, @criteria, config: @config)
93
+ @elo_rounds_played += 1
94
+
95
+ elo_report = EloRound.call(@players, @criteria, config: @config)
96
+
97
+ @elo_round_battle_count += elo_report[:battles_count]
98
+
99
+ elo_report
77
100
  end
78
101
 
79
102
  def eliminate_relegation_players!
80
- @players.calc_relegation_tier.each { |player| player.eliminated = 'relegation_tier' }
103
+ @players.calc_relegation_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION }
104
+ end
105
+
106
+ def rebalance_players!(elo_report)
107
+ return if elo_report[:highest_elo_diff].negative?
108
+
109
+ @players.eligible.each do |player|
110
+ next if elo_report[:players_in_round].include?(player.id)
111
+
112
+ player.elo += elo_report[:highest_elo_diff]
113
+ end
81
114
  end
82
115
 
83
116
  def run_free_for_all!
84
- FreeForAll.call(@players, @criteria, config: @config)
117
+ ffa_report = FreeForAll.call(@players, @criteria, config: @config)
118
+
119
+ @free_for_all_battle_count += ffa_report[:battles_count]
120
+ end
121
+
122
+ def report
123
+ {
124
+ ranking_id: ranking_id,
125
+ players_count: @players.size,
126
+ variation_too_high: @players.select { |player| player.eliminated == ELIMINATION_VARIATION }.size,
127
+ elo_rounds_played: @elo_rounds_played,
128
+ elo_round_battle_count: @elo_round_battle_count,
129
+ relegation_tier: @players.select { |player| player.eliminated == ELIMINATION_RELEGATION }.size,
130
+ ffa_round_battle_count: @free_for_all_battle_count,
131
+ top3: @players.eligible[0..2].map(&:id),
132
+ total_tokens: @total_tokens,
133
+ duration_seconds: Time.now - @start_time,
134
+ }
135
+ end
136
+
137
+ def final_logs
138
+ ActiveGenie::Logger.debug({ code: :ranking_final, players: @players.sorted.map(&:to_h) })
139
+ ActiveGenie::Logger.info({ code: :ranking, **report })
85
140
  end
86
141
 
87
142
  def log_context