active_genie 0.0.24 → 0.0.25

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -50
  3. data/VERSION +1 -1
  4. data/lib/active_genie/battle/README.md +5 -5
  5. data/lib/active_genie/battle/generalist.rb +132 -0
  6. data/lib/active_genie/battle.rb +6 -5
  7. data/lib/active_genie/clients/providers/anthropic_client.rb +77 -0
  8. data/lib/active_genie/clients/{base_client.rb → providers/base_client.rb} +74 -100
  9. data/lib/active_genie/clients/providers/deepseek_client.rb +91 -0
  10. data/lib/active_genie/clients/providers/google_client.rb +132 -0
  11. data/lib/active_genie/clients/providers/openai_client.rb +96 -0
  12. data/lib/active_genie/clients/unified_client.rb +42 -12
  13. data/lib/active_genie/concerns/loggable.rb +11 -23
  14. data/lib/active_genie/config/battle_config.rb +8 -0
  15. data/lib/active_genie/config/data_extractor_config.rb +23 -0
  16. data/lib/active_genie/config/llm_config.rb +36 -0
  17. data/lib/active_genie/config/log_config.rb +44 -0
  18. data/lib/active_genie/config/providers/anthropic_config.rb +57 -0
  19. data/lib/active_genie/config/providers/deepseek_config.rb +50 -0
  20. data/lib/active_genie/config/providers/google_config.rb +52 -0
  21. data/lib/active_genie/config/providers/openai_config.rb +50 -0
  22. data/lib/active_genie/config/providers/provider_base.rb +89 -0
  23. data/lib/active_genie/config/providers_config.rb +62 -0
  24. data/lib/active_genie/config/ranking_config.rb +21 -0
  25. data/lib/active_genie/config/scoring_config.rb +8 -0
  26. data/lib/active_genie/configuration.rb +51 -28
  27. data/lib/active_genie/data_extractor/README.md +13 -13
  28. data/lib/active_genie/data_extractor/from_informal.rb +54 -48
  29. data/lib/active_genie/data_extractor/generalist.md +12 -0
  30. data/lib/active_genie/data_extractor/generalist.rb +125 -0
  31. data/lib/active_genie/data_extractor.rb +7 -5
  32. data/lib/active_genie/errors/invalid_provider_error.rb +41 -0
  33. data/lib/active_genie/logger.rb +17 -66
  34. data/lib/active_genie/ranking/README.md +31 -1
  35. data/lib/active_genie/ranking/elo_round.rb +107 -104
  36. data/lib/active_genie/ranking/free_for_all.rb +78 -74
  37. data/lib/active_genie/ranking/player.rb +79 -71
  38. data/lib/active_genie/ranking/players_collection.rb +83 -71
  39. data/lib/active_genie/ranking/ranking.rb +71 -94
  40. data/lib/active_genie/ranking/ranking_scoring.rb +71 -50
  41. data/lib/active_genie/ranking.rb +2 -0
  42. data/lib/active_genie/scoring/README.md +4 -4
  43. data/lib/active_genie/scoring/generalist.rb +171 -0
  44. data/lib/active_genie/scoring/recommended_reviewers.rb +70 -71
  45. data/lib/active_genie/scoring.rb +8 -5
  46. data/lib/active_genie.rb +23 -1
  47. data/lib/tasks/benchmark.rake +10 -9
  48. data/lib/tasks/install.rake +3 -1
  49. data/lib/tasks/templates/active_genie.rb +11 -6
  50. metadata +31 -22
  51. data/lib/active_genie/battle/basic.rb +0 -129
  52. data/lib/active_genie/clients/anthropic_client.rb +0 -84
  53. data/lib/active_genie/clients/google_client.rb +0 -135
  54. data/lib/active_genie/clients/helpers/retry.rb +0 -29
  55. data/lib/active_genie/clients/openai_client.rb +0 -98
  56. data/lib/active_genie/configuration/log_config.rb +0 -14
  57. data/lib/active_genie/configuration/providers/anthropic_config.rb +0 -54
  58. data/lib/active_genie/configuration/providers/base_config.rb +0 -85
  59. data/lib/active_genie/configuration/providers/deepseek_config.rb +0 -54
  60. data/lib/active_genie/configuration/providers/google_config.rb +0 -56
  61. data/lib/active_genie/configuration/providers/internal_company_api_config.rb +0 -54
  62. data/lib/active_genie/configuration/providers/openai_config.rb +0 -54
  63. data/lib/active_genie/configuration/providers_config.rb +0 -40
  64. data/lib/active_genie/configuration/runtime_config.rb +0 -35
  65. data/lib/active_genie/data_extractor/basic.rb +0 -101
  66. data/lib/active_genie/scoring/basic.rb +0 -170
@@ -1,134 +1,137 @@
1
- require_relative '../battle/basic'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActiveGenie::Ranking
4
- class EloRound
5
- def self.call(...)
6
- new(...).call
7
- end
3
+ require_relative '../battle/generalist'
8
4
 
9
- def initialize(players, criteria, config: {})
10
- @players = players
11
- @relegation_tier = players.calc_relegation_tier
12
- @defender_tier = players.calc_defender_tier
13
- @criteria = criteria
14
- @config = config
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
20
- end
5
+ module ActiveGenie
6
+ module Ranking
7
+ class EloRound
8
+ def self.call(...)
9
+ new(...).call
10
+ end
21
11
 
22
- def call
23
- ActiveGenie::Logger.with_context(log_context) do
24
- save_previous_elo
25
- matches.each do |player_1, player_2|
26
- # TODO: battle can take a while, can be parallelized
27
- winner, loser = battle(player_1, player_2)
28
- next if winner.nil? || loser.nil?
12
+ def initialize(players, criteria, config: {})
13
+ @players = players
14
+ @relegation_tier = players.calc_relegation_tier
15
+ @defender_tier = players.calc_defender_tier
16
+ @criteria = criteria
17
+ @config = config
18
+ @tmp_defenders = []
19
+ @start_time = Time.now
20
+ @total_tokens = 0
21
+ @previous_elo = {}
22
+ @previous_highest_elo = @defender_tier.max_by(&:elo).elo
23
+ end
29
24
 
30
- winner.elo = calculate_new_elo(winner.elo, loser.elo, 1)
31
- loser.elo = calculate_new_elo(loser.elo, winner.elo, 0)
25
+ def call
26
+ ActiveGenie::Logger.with_context(log_context) do
27
+ save_previous_elo
28
+ matches.each do |player_1, player_2|
29
+ # TODO: battle can take a while, can be parallelized
30
+ winner, loser = battle(player_1, player_2)
31
+ next if winner.nil? || loser.nil?
32
+
33
+ winner.elo = calculate_new_elo(winner.elo, loser.elo, 1)
34
+ loser.elo = calculate_new_elo(loser.elo, winner.elo, 0)
35
+ end
32
36
  end
33
- end
34
37
 
35
- ActiveGenie::Logger.info({ code: :elo_round_report, **report })
38
+ ActiveGenie::Logger.call({ code: :elo_round_report, **report })
36
39
 
37
- report
38
- end
40
+ report
41
+ end
39
42
 
40
- private
43
+ private
41
44
 
42
- BATTLE_PER_PLAYER = 3
43
- K = 32
45
+ BATTLE_PER_PLAYER = 3
46
+ K = 32
44
47
 
45
- def save_previous_elo
46
- @previous_elo = @players.map { |player| [player.id, player.elo] }.to_h
47
- end
48
+ def save_previous_elo
49
+ @previous_elo = @players.map { |player| [player.id, player.elo] }.to_h
50
+ end
48
51
 
49
- def matches
50
- @relegation_tier.reduce([]) do |matches, attack_player|
51
- BATTLE_PER_PLAYER.times do
52
- matches << [attack_player, next_defense_player].shuffle
52
+ def matches
53
+ @relegation_tier.each_with_object([]) do |attack_player, matches|
54
+ BATTLE_PER_PLAYER.times do
55
+ matches << [attack_player, next_defense_player].shuffle
56
+ end
53
57
  end
54
- matches
55
58
  end
56
- end
57
59
 
58
- def next_defense_player
59
- @tmp_defenders = @defender_tier.shuffle if @tmp_defenders.size.zero?
60
+ def next_defense_player
61
+ @tmp_defenders = @defender_tier.shuffle if @tmp_defenders.empty?
60
62
 
61
- @tmp_defenders.pop
62
- end
63
+ @tmp_defenders.pop
64
+ end
63
65
 
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]
66
+ def battle(player_1, player_2)
67
+ ActiveGenie::Logger.with_context({ player_1_id: player_1.id, player_2_id: player_2.id }) do
68
+ result = ActiveGenie::Battle.call(
69
+ player_1.content,
70
+ player_2.content,
71
+ @criteria,
72
+ config: @config
73
+ )
74
+
75
+ winner, loser = case result['winner']
76
+ when 'player_1' then [player_1, player_2]
77
+ when 'player_2' then [player_2, player_1]
78
+ when 'draw' then [nil, nil]
79
+ end
80
+
81
+ [winner, loser]
77
82
  end
78
-
79
- [winner, loser]
80
83
  end
81
- end
82
84
 
83
- # INFO: Read more about the Elo rating system on https://en.wikipedia.org/wiki/Elo_rating_system
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
88
- end
85
+ # INFO: Read more about the Elo rating system on https://en.wikipedia.org/wiki/Elo_rating_system
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))
89
88
 
90
- def log_context
91
- { elo_round_id: }
92
- end
89
+ player_rating + (K * (score - expected_score)).round
90
+ end
93
91
 
94
- def elo_round_id
95
- relegation_tier_ids = @relegation_tier.map(&:id).join(',')
96
- defender_tier_ids = @defender_tier.map(&:id).join(',')
92
+ def log_context
93
+ { elo_round_id: }
94
+ end
97
95
 
98
- ranking_unique_key = [relegation_tier_ids, defender_tier_ids, @criteria, @config.to_json].join('-')
99
- Digest::MD5.hexdigest(ranking_unique_key)
100
- end
96
+ def elo_round_id
97
+ relegation_tier_ids = @relegation_tier.map(&:id).join(',')
98
+ defender_tier_ids = @defender_tier.map(&:id).join(',')
101
99
 
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
100
+ ranking_unique_key = [relegation_tier_ids, defender_tier_ids, @criteria, @config.to_json].join('-')
101
+ Digest::MD5.hexdigest(ranking_unique_key)
102
+ end
115
103
 
116
- def players_in_round
117
- @defender_tier + @relegation_tier
118
- end
104
+ def report
105
+ {
106
+ elo_round_id:,
107
+ players_in_round: players_in_round.map(&:id),
108
+ battles_count: matches.size,
109
+ duration_seconds: Time.now - @start_time,
110
+ total_tokens: @total_tokens,
111
+ previous_highest_elo: @previous_highest_elo,
112
+ highest_elo:,
113
+ highest_elo_diff: highest_elo - @previous_highest_elo,
114
+ players_elo_diff:
115
+ }
116
+ end
119
117
 
120
- def highest_elo
121
- players_in_round.max_by(&:elo).elo
122
- end
118
+ def players_in_round
119
+ @defender_tier + @relegation_tier
120
+ end
123
121
 
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
122
+ def highest_elo
123
+ players_in_round.max_by(&:elo).elo
124
+ end
129
125
 
130
- def log_observer(log)
131
- @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
126
+ def players_elo_diff
127
+ players_in_round.map do |player|
128
+ [player.id, player.elo - @previous_elo[player.id]]
129
+ end.sort_by { |_, diff| -diff }.to_h
130
+ end
131
+
132
+ def log_observer(log)
133
+ @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
134
+ end
132
135
  end
133
136
  end
134
137
  end
@@ -1,93 +1,97 @@
1
- require_relative '../battle/basic'
1
+ # frozen_string_literal: true
2
2
 
3
- module ActiveGenie::Ranking
4
- class FreeForAll
5
- def self.call(...)
6
- new(...).call
7
- end
3
+ require_relative '../battle/generalist'
8
4
 
9
- def initialize(players, criteria, config: {})
10
- @players = players
11
- @criteria = criteria
12
- @config = config
13
- @start_time = Time.now
14
- @total_tokens = 0
15
- end
5
+ module ActiveGenie
6
+ module Ranking
7
+ class FreeForAll
8
+ def self.call(...)
9
+ new(...).call
10
+ end
16
11
 
17
- def call
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)
21
-
22
- if winner.nil? || loser.nil?
23
- player_1.draw!
24
- player_2.draw!
25
- else
26
- winner.win!
27
- loser.lose!
28
- end
29
- end
12
+ def initialize(players, criteria, config: {})
13
+ @players = players
14
+ @criteria = criteria
15
+ @config = config
16
+ @start_time = Time.now
17
+ @total_tokens = 0
30
18
  end
31
19
 
32
- ActiveGenie::Logger.info({ code: :free_for_all_report, **report })
20
+ def call
21
+ ActiveGenie::Logger.with_context(log_context, observer: method(:log_observer)) do
22
+ matches.each do |player_1, player_2|
23
+ winner, loser = battle(player_1, player_2)
33
24
 
34
- report
35
- end
25
+ if winner.nil? || loser.nil?
26
+ player_1.draw!
27
+ player_2.draw!
28
+ else
29
+ winner.win!
30
+ loser.lose!
31
+ end
32
+ end
33
+ end
36
34
 
37
- private
35
+ ActiveGenie::Logger.call({ code: :free_for_all_report, **report })
38
36
 
39
- # TODO: reduce the number of matches based on transitivity.
40
- # For example, if A is better than B, and B is better than C, then battle between A and C should be auto win A
41
- def matches
42
- @players.eligible.combination(2).to_a
43
- end
37
+ report
38
+ end
39
+
40
+ private
44
41
 
45
- def battle(player_1, player_2)
46
- result = ActiveGenie::Battle.basic(
47
- player_1.content,
48
- player_2.content,
49
- @criteria,
50
- config: @config
51
- )
52
-
53
- winner, loser = case result['winner']
54
- when 'player_1' then [player_1, player_2, result['reasoning']]
55
- when 'player_2' then [player_2, player_1, result['reasoning']]
56
- when 'draw' then [nil, nil, result['reasoning']]
42
+ # TODO: reduce the number of matches based on transitivity.
43
+ # For example, if A is better than B, and B is better than C, then battle between A and C should be auto win A
44
+ def matches
45
+ @players.eligible.combination(2).to_a
57
46
  end
58
47
 
59
- ActiveGenie::Logger.debug({
60
- code: :free_for_all_battle,
61
- player_ids: [player_1.id, player_2.id],
62
- winner_id: winner&.id,
63
- loser_id: loser&.id,
64
- reasoning: result['reasoning']
65
- })
48
+ def battle(player_1, player_2)
49
+ result = ActiveGenie::Battle.call(
50
+ player_1.content,
51
+ player_2.content,
52
+ @criteria,
53
+ config: @config
54
+ )
66
55
 
67
- [winner, loser]
68
- end
56
+ winner, loser = case result['winner']
57
+ when 'player_1' then [player_1, player_2, result['reasoning']]
58
+ when 'player_2' then [player_2, player_1, result['reasoning']]
59
+ when 'draw' then [nil, nil, result['reasoning']]
60
+ end
69
61
 
70
- def log_context
71
- { free_for_all_id: }
72
- end
62
+ ActiveGenie::Logger.call({
63
+ code: :free_for_all_battle,
64
+ player_ids: [player_1.id, player_2.id],
65
+ winner_id: winner&.id,
66
+ loser_id: loser&.id,
67
+ reasoning: result['reasoning']
68
+ })
73
69
 
74
- def free_for_all_id
75
- eligible_ids = @players.eligible.map(&:id).join(',')
76
- ranking_unique_key = [eligible_ids, @criteria, @config.to_json].join('-')
77
- Digest::MD5.hexdigest(ranking_unique_key)
78
- end
70
+ [winner, loser]
71
+ end
79
72
 
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
73
+ def log_context
74
+ { free_for_all_id: }
75
+ end
88
76
 
89
- def log_observer(log)
90
- @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
77
+ def free_for_all_id
78
+ eligible_ids = @players.eligible.map(&:id).join(',')
79
+ ranking_unique_key = [eligible_ids, @criteria, @config.to_json].join('-')
80
+ Digest::MD5.hexdigest(ranking_unique_key)
81
+ end
82
+
83
+ def report
84
+ {
85
+ free_for_all_id:,
86
+ battles_count: matches.size,
87
+ duration_seconds: Time.now - @start_time,
88
+ total_tokens: @total_tokens
89
+ }
90
+ end
91
+
92
+ def log_observer(log)
93
+ @total_tokens += log[:total_tokens] if log[:code] == :llm_usage
94
+ end
91
95
  end
92
96
  end
93
97
  end
@@ -1,92 +1,100 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest'
2
4
 
3
- module ActiveGenie::Ranking
4
- class Player
5
- def initialize(params)
6
- params = { content: params } if params.is_a?(String)
7
-
8
- @content = params.dig(:content) || params
9
- @id = params.dig(:id) || Digest::MD5.hexdigest(@content)
10
- @score = params.dig(:score) || nil
11
- @elo = params.dig(:elo) || nil
12
- @ffa_win_count = params.dig(:ffa_win_count) || 0
13
- @ffa_lose_count = params.dig(:ffa_lose_count) || 0
14
- @ffa_draw_count = params.dig(:ffa_draw_count) || 0
15
- @eliminated = params.dig(:eliminated) || nil
16
- end
5
+ module ActiveGenie
6
+ module Ranking
7
+ class Player
8
+ def initialize(params)
9
+ params = { content: params } if params.is_a?(String)
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
20
+ end
21
+ attr_accessor :rank
17
22
 
18
- attr_reader :id, :content, :score, :elo,
19
- :ffa_win_count, :ffa_lose_count, :ffa_draw_count,
20
- :eliminated
21
- attr_accessor :rank
23
+ def score=(value)
24
+ ActiveGenie::Logger.call({ code: :new_score, player_id: id, score: value }) if value != @score
25
+ @score = value
26
+ @elo = generate_elo_by_score
27
+ end
22
28
 
23
- def score=(value)
24
- ActiveGenie::Logger.debug({ code: :new_score, player_id: id, score: value }) if value != @score
25
- @score = value
26
- @elo = generate_elo_by_score
27
- end
29
+ def elo=(value)
30
+ ActiveGenie::Logger.call({ code: :new_elo, player_id: id, elo: value }) if value != @elo
31
+ @elo = value
32
+ end
28
33
 
29
- def elo=(value)
30
- ActiveGenie::Logger.debug({ code: :new_elo, player_id: id, elo: value }) if value != @elo
31
- @elo = value
32
- end
34
+ def eliminated=(value)
35
+ ActiveGenie::Logger.call({ code: :new_eliminated, player_id: id, eliminated: value }) if value != @eliminated
36
+ @eliminated = value
37
+ end
33
38
 
34
- def eliminated=(value)
35
- ActiveGenie::Logger.debug({ code: :new_eliminated, player_id: id, eliminated: value }) if value != @eliminated
36
- @eliminated = value
37
- end
39
+ def draw!
40
+ @ffa_draw_count += 1
41
+ ActiveGenie::Logger.call({ code: :new_ffa_score, player_id: id, result: 'draw', ffa_score: })
42
+ end
38
43
 
39
- def draw!
40
- @ffa_draw_count += 1
41
- ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'draw', ffa_score: })
42
- end
44
+ def win!
45
+ @ffa_win_count += 1
46
+ ActiveGenie::Logger.call({ code: :new_ffa_score, player_id: id, result: 'win', ffa_score: })
47
+ end
43
48
 
44
- def win!
45
- @ffa_win_count += 1
46
- ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'win', ffa_score: })
47
- end
49
+ def lose!
50
+ @ffa_lose_count += 1
51
+ ActiveGenie::Logger.call({ code: :new_ffa_score, player_id: id, result: 'lose', ffa_score: })
52
+ end
48
53
 
49
- def lose!
50
- @ffa_lose_count += 1
51
- ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'lose', ffa_score: })
52
- end
54
+ attr_reader :id, :content, :score, :elo, :ffa_win_count, :ffa_lose_count, :ffa_draw_count, :eliminated, :name
53
55
 
54
- def ffa_score
55
- @ffa_win_count * 3 + @ffa_draw_count
56
- end
56
+ def ffa_score
57
+ @ffa_win_count * 3 + @ffa_draw_count
58
+ end
57
59
 
58
- def to_h
59
- {
60
- id:, content:, score:, elo:,
61
- ffa_win_count:, ffa_lose_count:, ffa_draw_count:,
62
- eliminated:, ffa_score:
63
- }
64
- end
60
+ def sort_value
61
+ (ffa_score * 1_000_000) + ((elo || 0) * 100) + (score || 0)
62
+ end
63
+
64
+ def to_json(*_args)
65
+ to_h.to_json
66
+ end
67
+
68
+ def to_h
69
+ {
70
+ id:, name:, content:, score:, elo:,
71
+ ffa_win_count:, ffa_lose_count:, ffa_draw_count:,
72
+ eliminated:, ffa_score:, sort_value:
73
+ }
74
+ end
75
+
76
+ def method_missing(method_name, *args, &block)
77
+ if method_name == :[] && args.size == 1
78
+ attr_name = args.first.to_sym
65
79
 
66
- def method_missing(method_name, *args, &block)
67
- if method_name == :[] && args.size == 1
68
- attr_name = args.first.to_sym
80
+ return send(attr_name) if respond_to?(attr_name)
69
81
 
70
- if respond_to?(attr_name)
71
- return send(attr_name)
72
- else
73
82
  return nil
83
+
74
84
  end
85
+
86
+ super
75
87
  end
76
88
 
77
- super
78
- end
89
+ def respond_to_missing?(method_name, include_private = false)
90
+ method_name == :[] || super
91
+ end
79
92
 
80
- def respond_to_missing?(method_name, include_private = false)
81
- method_name == :[] || super
82
- end
93
+ def generate_elo_by_score
94
+ BASE_ELO + ((@score || 0) - 50)
95
+ end
83
96
 
84
- def generate_elo_by_score
85
- BASE_ELO + ((@score || 0) - 50)
97
+ BASE_ELO = 1000
86
98
  end
87
-
88
- private
89
-
90
- BASE_ELO = 1000
91
99
  end
92
- end
100
+ end