active_genie 0.0.10 → 0.0.12

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.
@@ -25,15 +25,15 @@ module ActiveGenie::Scoring
25
25
  # @return [Hash] The evaluation result containing the scores and reasoning
26
26
  # @return [Number] :final_score The final score of the text based on the criteria and reviewers
27
27
  # @return [String] :final_reasoning Detailed explanation of why the final score was reached
28
- def self.call(text, criteria, reviewers = [], config: {})
29
- new(text, criteria, reviewers, config:).call
28
+ def self.call(...)
29
+ new(...).call
30
30
  end
31
31
 
32
32
  def initialize(text, criteria, reviewers = [], config: {})
33
33
  @text = text
34
34
  @criteria = criteria
35
35
  @reviewers = Array(reviewers).compact.uniq
36
- @config = config
36
+ @config = ActiveGenie::Configuration.to_h(config)
37
37
  end
38
38
 
39
39
  def call
@@ -76,7 +76,12 @@ module ActiveGenie::Scoring
76
76
  }
77
77
  }
78
78
 
79
- ::ActiveGenie::Clients::UnifiedClient.function_calling(messages, function, config:)
79
+ ::ActiveGenie::Clients::UnifiedClient.function_calling(
80
+ messages,
81
+ function,
82
+ model_tier: 'lower_tier',
83
+ config: @config
84
+ )
80
85
  end
81
86
 
82
87
  private
@@ -85,42 +90,62 @@ module ActiveGenie::Scoring
85
90
  @get_or_recommend_reviewers ||= if @reviewers.count > 0
86
91
  @reviewers
87
92
  else
88
- recommended_reviews = RecommendedReviews.call(@text, @criteria, config:)
93
+ result = RecommendedReviewers.call(@text, @criteria, config: @config)
89
94
 
90
- [recommended_reviews[:reviewer1], recommended_reviews[:reviewer2], recommended_reviews[:reviewer3]]
95
+ [result['reviewer1'], result['reviewer2'], result['reviewer3']]
91
96
  end
92
97
  end
93
98
 
94
99
  PROMPT = <<~PROMPT
95
- Evaluate and score the provided text based on predefined criteria, which may include rules, keywords, or patterns. Use a scoring range of 0 to 100, with 100 representing the highest possible score. Follow the instructions below to ensure an accurate and objective assessment.
100
+ Evaluate and score the provided text based on predefined criteria, using a scoring range of 0 to 100 with 100 representing the highest possible score.
101
+
102
+ Follow the instructions below to ensure a comprehensive and objective assessment.
96
103
 
97
104
  # Evaluation Process
98
- 1. **Analysis**: Thoroughly compare the text against each criterion to ensure comprehensive evaluation.
99
- 2. **Document Deviations**: Clearly identify and document any areas where the content does not align with the specified criteria.
100
- 3. **Highlight Strengths**: Emphasize notable features or elements that enhance the overall quality or effectiveness of the content.
101
- 4. **Identify Weaknesses**: Specify areas where the content fails to meet the criteria or where improvements could be made.
102
105
 
103
- # Output Requirements
104
- Provide a detailed review, including:
105
- - A final score (0-100)
106
- - Specific reasoning for the assigned score, covering all evaluated criteria.
107
- - Ensure the reasoning includes both positive aspects and suggested improvements.
106
+ 1. **Analysis**:
107
+ - Thoroughly compare the text against each criterion for a comprehensive evaluation.
108
+
109
+ 2. **Document Deviations**:
110
+ - Identify and document areas where the content does not align with the specified criteria.
111
+
112
+ 3. **Highlight Strengths**:
113
+ - Note notable features or elements that enhance the quality or effectiveness of the content.
114
+
115
+ 4. **Identify Weaknesses**:
116
+ - Specify areas where the content fails to meet the criteria or where improvements could be made.
117
+
118
+ # Scoring Fairness
119
+
120
+ - Ensure the assigned score reflects both the alignment with the criteria and the content's effectiveness.
121
+ - Consider if the fulfillment of other criteria compensates for areas lacking extreme details.
122
+
123
+ # Scoring Range
124
+
125
+ Segment scores into five parts before assigning a final score:
126
+ - **Terrible**: 0-20 - Content does not meet the criteria.
127
+ - **Bad**: 21-40 - Content is substandard but meets some criteria.
128
+ - **Average**: 41-60 - Content meets criteria with room for improvement.
129
+ - **Good**: 61-80 - Content exceeds criteria and is above average.
130
+ - **Great**: 81-100 - Content exceeds all expectations.
108
131
 
109
132
  # Guidelines
110
- - Maintain objectivity, avoiding biases or preconceived notions.
111
- - Deconstruct each criterion into actionable components for a systematic evaluation.
112
- - If the text lacks information, apply reasonable judgment to assign a score while clearly explaining the rationale.
113
- PROMPT
114
133
 
115
- def config
116
- {
117
- all_providers: { model_tier: 'lower_tier' },
118
- log: {
119
- **(@config.dig(:log) || {}),
120
- trace: self.class.name,
121
- },
122
- **@config
123
- }
124
- end
134
+ - Maintain objectivity and avoid biases.
135
+ - Deconstruct each criterion into actionable components for systematic evaluation.
136
+ - Apply reasonable judgment in assigning a score, justifying your rationale clearly.
137
+
138
+ # Output Format
139
+
140
+ - Provide a detailed review including:
141
+ - A final score (0-100)
142
+ - Specific reasoning for the assigned score, detailing all evaluated criteria
143
+ - Include both positive aspects and suggested improvements
144
+
145
+ # Notes
146
+
147
+ - Consider edge cases where the text may partially align with criteria.
148
+ - If lacking information, reasonably judge and explain your scoring approach.
149
+ PROMPT
125
150
  end
126
151
  end
@@ -3,7 +3,7 @@
3
3
  require_relative '../clients/unified_client'
4
4
 
5
5
  module ActiveGenie::Scoring
6
- # The RecommendedReviews class intelligently suggests appropriate reviewer roles
6
+ # The RecommendedReviewers class intelligently suggests appropriate reviewer roles
7
7
  # for evaluating text content based on specific criteria. It uses AI to analyze
8
8
  # the content and criteria to identify the most suitable subject matter experts.
9
9
  #
@@ -11,14 +11,14 @@ module ActiveGenie::Scoring
11
11
  # three distinct reviewer roles with complementary expertise and perspectives.
12
12
  #
13
13
  # @example Getting recommended reviewers for technical content
14
- # RecommendedReviews.call("Technical documentation about API design",
14
+ # RecommendedReviewers.call("Technical documentation about API design",
15
15
  # "Evaluate technical accuracy and clarity")
16
16
  # # => { reviewer1: "API Architect", reviewer2: "Technical Writer",
17
17
  # # reviewer3: "Developer Advocate", reasoning: "..." }
18
18
  #
19
- class RecommendedReviews
20
- def self.call(text, criteria, config: {})
21
- new(text, criteria, config:).call
19
+ class RecommendedReviewers
20
+ def self.call(...)
21
+ new(...).call
22
22
  end
23
23
 
24
24
  # Initializes a new reviewer recommendation instance
@@ -29,7 +29,7 @@ module ActiveGenie::Scoring
29
29
  def initialize(text, criteria, config: {})
30
30
  @text = text
31
31
  @criteria = criteria
32
- @config = config
32
+ @config = ActiveGenie::Configuration.to_h(config)
33
33
  end
34
34
 
35
35
  def call
@@ -53,7 +53,14 @@ module ActiveGenie::Scoring
53
53
  }
54
54
  }
55
55
 
56
- ::ActiveGenie::Clients::UnifiedClient.function_calling(messages, function, config: @config)
56
+ result = client.function_calling(
57
+ messages,
58
+ function,
59
+ model_tier: 'lower_tier',
60
+ config: @config
61
+ )
62
+
63
+ result
57
64
  end
58
65
 
59
66
  private
@@ -72,5 +79,9 @@ module ActiveGenie::Scoring
72
79
  - Include reasoning for how each choice supports a thorough and insightful review.
73
80
  - Avoid redundant or overly similar titles/roles to maintain diversity.
74
81
  PROMPT
82
+
83
+ def client
84
+ ::ActiveGenie::Clients::UnifiedClient
85
+ end
75
86
  end
76
87
  end
@@ -1,5 +1,5 @@
1
1
  require_relative 'scoring/basic'
2
- require_relative 'scoring/recommended_reviews'
2
+ require_relative 'scoring/recommended_reviewers'
3
3
 
4
4
  module ActiveGenie
5
5
  # See the [Scoring README](lib/active_genie/scoring/README.md) for more information.
@@ -10,8 +10,8 @@ module ActiveGenie
10
10
  Basic.call(...)
11
11
  end
12
12
 
13
- def recommended_reviews(...)
14
- RecommendedReviews.call(...)
13
+ def recommended_reviewers(...)
14
+ RecommendedReviewers.call(...)
15
15
  end
16
16
  end
17
17
  end
data/lib/active_genie.rb CHANGED
@@ -5,7 +5,7 @@ module ActiveGenie
5
5
  autoload :DataExtractor, File.join(__dir__, 'active_genie/data_extractor')
6
6
  autoload :Battle, File.join(__dir__, 'active_genie/battle')
7
7
  autoload :Scoring, File.join(__dir__, 'active_genie/scoring')
8
- autoload :Leaderboard, File.join(__dir__, 'active_genie/leaderboard')
8
+ autoload :Ranking, File.join(__dir__, 'active_genie/ranking')
9
9
 
10
10
  class << self
11
11
  def configure
metadata CHANGED
@@ -1,57 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_genie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radamés Roriz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-10 00:00:00.000000000 Z
11
+ date: 2025-03-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: "# ActiveGenie \U0001F9DE‍♂️\n> Transform your Ruby application with
14
- powerful, production-ready GenAI features\n\n[![Gem Version](https://badge.fury.io/rb/active_genie.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/active_genie)\n[![Ruby](https://github.com/roriz/active_genie/actions/workflows/ruby.yml/badge.svg)](https://github.com/roriz/active_genie/actions/workflows/ruby.yml)\n\nActiveGenie
13
+ description: "# ActiveGenie \U0001F9DE‍♂️\n> The lodash for GenAI, stop reinventing
14
+ the wheel\n\n[![Gem Version](https://badge.fury.io/rb/active_genie.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/active_genie)\n[![Ruby](https://github.com/roriz/active_genie/actions/workflows/ruby.yml/badge.svg)](https://github.com/roriz/active_genie/actions/workflows/ruby.yml)\n\nActiveGenie
15
15
  is a Ruby gem that provides a polished, production-ready interface for working with
16
- Generative AI (GenAI) models. Just like ActiveStorage simplifies file handling in
17
- Rails, ActiveGenie makes it effortless to integrate GenAI capabilities into your
18
- Ruby applications.\n\n## Features\n\n- \U0001F3AF **Data Extraction**: Extract structured
19
- data from unstructured text with type validation\n- \U0001F4CA **Smart Scoring**:
20
- Multi-reviewer evaluation system with automatic expert selection\n- \U0001F4AD **Leaderboard**:
21
- Consistent rank items based on custom criteria, using multiple tecniques of ranking\n\n##
22
- Installation\n\n1. Add to your Gemfile:\n```ruby\ngem 'active_genie'\n```\n\n2.
23
- Install the gem:\n```shell\nbundle install\n```\n\n3. Generate the configuration:\n```shell\necho
24
- \"ActiveGenie.load_tasks\" >> Rakefile\nrails g active_genie:install\n```\n\n4.
25
- Configure your credentials in `config/initializers/active_genie.rb`:\n```ruby\nActiveGenie.configure
26
- do |config|\n config.openai.api_key = ENV['OPENAI_API_KEY']\nend\n```\n\n## Quick
27
- Start\n\n### Data Extractor\nExtract structured data from text using AI-powered
28
- analysis, handling informal language and complex expressions.\n\n```ruby\ntext =
29
- \"Nike Air Max 90 - Size 42 - $199.99\"\nschema = {\n brand: { \n type: 'string',\n
30
- \ enum: [\"Nike\", \"Adidas\", \"Puma\"]\n },\n price: { \n type: 'number',\n
31
- \ minimum: 0\n },\n size: {\n type: 'integer',\n minimum: 35,\n maximum:
32
- 46\n }\n}\n\nresult = ActiveGenie::DataExtractor.call(text, schema)\n# => { \n#
33
- \ brand: \"Nike\", \n# brand_explanation: \"Brand name found at start of
34
- text\",\n# price: 199.99,\n# price_explanation: \"Price found in USD format
35
- at end\",\n# size: 42,\n# size_explanation: \"Size explicitly stated in
36
- the middle\"\n# }\n```\n\nFeatures:\n- Structured data extraction with type validation\n-
37
- Schema-based extraction with custom constraints\n- Informal text analysis (litotes,
38
- hedging)\n- Detailed explanations for extracted values\n\nSee the [Data Extractor
39
- README](lib/active_genie/data_extractor/README.md) for informal text processing,
40
- advanced schemas, and detailed interface documentation.\n\n### Scoring\nText evaluation
41
- system that provides detailed scoring and feedback using multiple expert reviewers.
42
- Get balanced scoring through AI-powered expert reviewers that automatically adapt
43
- to your content.\n\n```ruby\ntext = \"The code implements a binary search algorithm
44
- with O(log n) complexity\"\ncriteria = \"Evaluate technical accuracy and clarity\"\n\nresult
45
- = ActiveGenie::Scoring.basic(text, criteria)\n# => {\n# algorithm_expert_score:
46
- 95,\n# algorithm_expert_reasoning: \"Accurately describes binary search and
47
- its complexity\",\n# technical_writer_score: 90,\n# technical_writer_reasoning:
48
- \"Clear and concise explanation of the algorithm\",\n# final_score: 92.5\n#
49
- \ }\n```\n\nFeatures:\n- Multi-reviewer evaluation with automatic expert selection\n-
50
- Detailed feedback with scoring reasoning\n- Customizable reviewer weights\n- Flexible
51
- evaluation criteria\n\nSee the [Scoring README](lib/active_genie/scoring/README.md)
16
+ Generative AI (GenAI) models. Just like Lodash or ActiveStorage, ActiveGenie simplifies
17
+ GenAI integration in your Ruby applications.\n\n## Features\n\n- \U0001F3AF **Data
18
+ Extraction**: Extract structured data from unstructured text with type validation\n-
19
+ \U0001F4CA **Data Scoring**: Multi-reviewer evaluation system\n- ⚔️ **Data Battle**:
20
+ Battle between two data like a political debate\n- \U0001F4AD **Data Ranking**:
21
+ Consistent rank data using scoring + elo ranking + battles\n\n## Installation\n\n1.
22
+ Add to your Gemfile:\n```ruby\ngem 'active_genie'\n```\n\n2. Install the gem:\n```shell\nbundle
23
+ install\n```\n\n3. Generate the configuration:\n```shell\necho \"ActiveGenie.load_tasks\"
24
+ >> Rakefile\nrails g active_genie:install\n```\n\n4. Configure your credentials
25
+ in `config/initializers/active_genie.rb`:\n```ruby\nActiveGenie.configure do |config|\n
26
+ \ config.openai.api_key = ENV['OPENAI_API_KEY']\nend\n```\n\n## Quick Start\n\n###
27
+ Data Extractor\nExtract structured data from text using AI-powered analysis, handling
28
+ informal language and complex expressions.\n\n```ruby\ntext = \"Nike Air Max 90
29
+ - Size 42 - $199.99\"\nschema = {\n brand: { \n type: 'string',\n enum: [\"Nike\",
30
+ \"Adidas\", \"Puma\"]\n },\n price: { \n type: 'number',\n minimum: 0\n
31
+ \ },\n size: {\n type: 'integer',\n minimum: 35,\n maximum: 46\n }\n}\n\nresult
32
+ = ActiveGenie::DataExtractor.call(text, schema)\n# => { \n# brand: \"Nike\",
33
+ \n# brand_explanation: \"Brand name found at start of text\",\n# price:
34
+ 199.99,\n# price_explanation: \"Price found in USD format at end\",\n# size:
35
+ 42,\n# size_explanation: \"Size explicitly stated in the middle\"\n# }\n```\n\nFeatures:\n-
36
+ Structured data extraction with type validation\n- Schema-based extraction with
37
+ custom constraints\n- Informal text analysis (litotes, hedging)\n- Detailed explanations
38
+ for extracted values\n\nSee the [Data Extractor README](lib/active_genie/data_extractor/README.md)
39
+ for informal text processing, advanced schemas, and detailed interface documentation.\n\n###
40
+ Data Scoring\nText evaluation system that provides detailed scoring and feedback
41
+ using multiple expert reviewers. Get balanced scoring through AI-powered expert
42
+ reviewers that automatically adapt to your content.\n\n```ruby\ntext = \"The code
43
+ implements a binary search algorithm with O(log n) complexity\"\ncriteria = \"Evaluate
44
+ technical accuracy and clarity\"\n\nresult = ActiveGenie::Scoring.basic(text, criteria)\n#
45
+ => {\n# algorithm_expert_score: 95,\n# algorithm_expert_reasoning: \"Accurately
46
+ describes binary search and its complexity\",\n# technical_writer_score: 90,\n#
47
+ \ technical_writer_reasoning: \"Clear and concise explanation of the algorithm\",\n#
48
+ \ final_score: 92.5\n# }\n```\n\nFeatures:\n- Multi-reviewer evaluation with
49
+ automatic expert selection\n- Detailed feedback with scoring reasoning\n- Customizable
50
+ reviewer weights\n- Flexible evaluation criteria\n\nSee the [Scoring README](lib/active_genie/scoring/README.md)
52
51
  for advanced usage, custom reviewers, and detailed interface documentation.\n\n###
53
- Battle\nAI-powered battle evaluation system that determines winners between two
54
- players based on specified criteria.\n\n```ruby\nrequire 'active_genie'\n\nplayer_a
52
+ Data Battle\nAI-powered battle evaluation system that determines winners between
53
+ two players based on specified criteria.\n\n```ruby\nrequire 'active_genie'\n\nplayer_a
55
54
  = \"Implementation uses dependency injection for better testability\"\nplayer_b
56
55
  = \"Code has high test coverage but tightly coupled components\"\ncriteria = \"Evaluate
57
56
  code quality and maintainability\"\n\nresult = ActiveGenie::Battle.call(player_a,
@@ -65,50 +64,29 @@ description: "# ActiveGenie \U0001F9DE‍♂️\n> Transform your Ruby applicati
65
64
  evaluation with automatic expert selection\n- Detailed feedback with scoring reasoning\n-
66
65
  Customizable reviewer weights\n- Flexible evaluation criteria\n\nSee the [Battle
67
66
  README](lib/active_genie/battle/README.md) for advanced usage, custom reviewers,
68
- and detailed interface documentation.\n\n### League\nThe League module provides
67
+ and detailed interface documentation.\n\n### Data Ranking\nThe Ranking module provides
69
68
  competitive ranking through multi-stage evaluation:\n\n\n```ruby\nrequire 'active_genie'\n\nplayers
70
69
  = ['REST API', 'GraphQL API', 'SOAP API', 'gRPC API', 'Websocket API']\ncriteria
71
- = \"Best one to be used into a high changing environment\"\n\nresult = ActiveGenie::League.call(players,
70
+ = \"Best one to be used into a high changing environment\"\n\nresult = ActiveGenie::Ranking.call(players,
72
71
  criteria)\n# => {\n# winner_player: \"gRPC API\",\n# reasoning: \"gRPC
73
72
  API is the best one to be used into a high changing environment\",\n# }\n```\n\n-
74
73
  **Multi-phase ranking system** combining expert scoring and ELO algorithms\n- **Automatic
75
74
  elimination** of inconsistent performers using statistical analysis\n- **Dynamic
76
75
  ranking adjustments** based on simulated pairwise battles, from bottom to top\n\nSee
77
- the [League README](lib/active_genie/league/README.md) for implementation details,
78
- configuration, and advanced ranking strategies.\n\n### Summarizer (WIP)\nThe summarizer
79
- is a tool that can be used to summarize a given text. It uses a set of rules to
80
- summarize the text out of the box. Uses the best practices of prompt engineering
81
- and engineering to make the summarization as accurate as possible.\n\n```ruby\nrequire
82
- 'active_genie'\n\ntext = \"Example text to be summarized. The fox jumps over the
83
- dog\"\nsummarized_text = ActiveGenie::Summarizer.call(text)\nputs summarized_text
84
- # => \"The fox jumps over the dog\"\n```\n\n### Language detector (WIP)\nThe language
85
- detector is a tool that can be used to detect the language of a given text. It uses
86
- a set of rules to detect the language of the text out of the box. Uses the best
87
- practices of prompt engineering and engineering to make the language detection as
88
- accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext = \"Example text
89
- to be detected\"\nlanguage = ActiveGenie::LanguageDetector.call(text)\nputs language
90
- # => \"en\"\n```\n\n### Translator (WIP)\nThe translator is a tool that can be used
91
- to translate a given text. It uses a set of rules to translate the text out of the
92
- box. Uses the best practices of prompt engineering and engineering to make the translation
93
- as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext = \"Example
94
- text to be translated\"\ntranslated_text = ActiveGenie::Translator.call(text, from:
95
- 'en', to: 'pt')\nputs translated_text # => \"Exemplo de texto a ser traduzido\"\n```\n\n###
96
- Sentiment analyzer (WIP)\nThe sentiment analyzer is a tool that can be used to analyze
97
- the sentiment of a given text. It uses a set of rules to analyze the sentiment of
98
- the text out of the box. Uses the best practices of prompt engineering and engineering
99
- to make the sentiment analysis as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext
100
- = \"Example text to be analyzed\"\nsentiment = ActiveGenie::SentimentAnalyzer.call(text)\nputs
101
- sentiment # => \"positive\"\n```\n\n## Configuration\n\n| Config | Description |
102
- Default |\n|--------|-------------|---------|\n| `provider` | LLM provider (openai,
103
- anthropic, etc) | `nil` |\n| `model` | Model to use | `nil` |\n| `api_key` | Provider
104
- API key | `nil` |\n| `timeout` | Request timeout in seconds | `5` |\n| `max_retries`
105
- | Maximum retry attempts | `3` |\n\n> **Note:** Each module can append its own set
106
- of configuration, see the individual module documentation for details.\n\n## Contributing\n\n1.
107
- Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3.
108
- Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch
109
- (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n## License\n\nThis
110
- project is licensed under the MIT License - see the [LICENSE](LICENSE) file for
111
- details.\n"
76
+ the [Ranking README](lib/active_genie/ranking/README.md) for implementation details,
77
+ configuration, and advanced ranking strategies.\n\n### Text Summarizer (Future)\n###
78
+ Language detector (Future)\n### Translator (Future)\n### Sentiment analyzer (Future)\n\n##
79
+ Configuration\n\n| Config | Description | Default |\n|--------|-------------|---------|\n|
80
+ `provider` | LLM provider (openai, anthropic, etc) | `nil` |\n| `model` | Model
81
+ to use | `nil` |\n| `api_key` | Provider API key | `nil` |\n| `timeout` | Request
82
+ timeout in seconds | `5` |\n| `max_retries` | Maximum retry attempts | `3` |\n\n>
83
+ **Note:** Each module can append its own set of configuration, see the individual
84
+ module documentation for details.\n\n## Contributing\n\n1. Fork the repository\n2.
85
+ Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit
86
+ your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git
87
+ push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nThis
88
+ project is licensed under the Apache License 2.0 License - see the [LICENSE](LICENSE)
89
+ file for details.\n"
112
90
  email:
113
91
  - radames@roriz.dev
114
92
  executables: []
@@ -132,18 +110,19 @@ files:
132
110
  - lib/active_genie/data_extractor/README.md
133
111
  - lib/active_genie/data_extractor/basic.rb
134
112
  - lib/active_genie/data_extractor/from_informal.rb
135
- - lib/active_genie/league.rb
136
- - lib/active_genie/league/README.md
137
- - lib/active_genie/league/elo_ranking.rb
138
- - lib/active_genie/league/free_for_all.rb
139
- - lib/active_genie/league/league.rb
140
- - lib/active_genie/league/player.rb
141
- - lib/active_genie/league/players_collection.rb
142
113
  - lib/active_genie/logger.rb
114
+ - lib/active_genie/ranking.rb
115
+ - lib/active_genie/ranking/README.md
116
+ - lib/active_genie/ranking/elo_round.rb
117
+ - lib/active_genie/ranking/free_for_all.rb
118
+ - lib/active_genie/ranking/player.rb
119
+ - lib/active_genie/ranking/players_collection.rb
120
+ - lib/active_genie/ranking/ranking.rb
121
+ - lib/active_genie/ranking/ranking_scoring.rb
143
122
  - lib/active_genie/scoring.rb
144
123
  - lib/active_genie/scoring/README.md
145
124
  - lib/active_genie/scoring/basic.rb
146
- - lib/active_genie/scoring/recommended_reviews.rb
125
+ - lib/active_genie/scoring/recommended_reviewers.rb
147
126
  - lib/tasks/install.rake
148
127
  - lib/tasks/templates/active_genie.rb
149
128
  homepage: https://github.com/Roriz/active_genie
@@ -1,121 +0,0 @@
1
- require_relative '../battle/basic'
2
-
3
- module ActiveGenie::Leaderboard
4
- class EloRanking
5
- def self.call(players, criteria, config: {})
6
- new(players, criteria, config:).call
7
- end
8
-
9
- def initialize(players, criteria, config: {})
10
- @players = players
11
- @criteria = criteria
12
- @config = config
13
- @start_time = Time.now
14
- end
15
-
16
- def call
17
- @players.each(&:generate_elo_by_score)
18
-
19
- round_count = 0
20
- while @players.eligible_size > MINIMAL_PLAYERS_TO_BATTLE
21
- round = create_round(@players.tier_relegation, @players.tier_defense)
22
-
23
- round.each do |player_a, player_b|
24
- winner, loser = battle(player_a, player_b) # This can take a while, can be parallelized
25
- update_elo(winner, loser)
26
- ActiveGenie::Logger.trace({ **log, step: :elo_battle, winner_id: winner.id, loser_id: loser.id, winner_elo: winner.elo, loser_elo: loser.elo })
27
- end
28
-
29
- eliminate_all_relegation_players
30
- round_count += 1
31
- end
32
-
33
- ActiveGenie::Logger.info({ **log, step: :elo_end, round_count:, eligible_size: @players.eligible_size })
34
- @players
35
- end
36
-
37
- private
38
-
39
- MATCHS_PER_PLAYER = 3
40
- LOSE_PENALTY = 15
41
- MINIMAL_PLAYERS_TO_BATTLE = 10
42
- K = 32
43
-
44
- # Create a round of matches
45
- # each round is exactly 1 regation player vs 3 defense players for all regation players
46
- # each match is unique (player vs player)
47
- # each defense player is battle exactly 3 times
48
- def create_round(relegation_players, defense_players)
49
- matches = []
50
-
51
- relegation_players.each do |player_a|
52
- player_enemies = []
53
- MATCHS_PER_PLAYER.times do
54
- defender = nil
55
- while defender.nil? || player_enemies.include?(defender.id)
56
- defender = defense_players.sample
57
- end
58
-
59
- matches << [player_a, defender].shuffle
60
- player_enemies << defender.id
61
- end
62
- end
63
-
64
- matches
65
- end
66
-
67
- def battle(player_a, player_b)
68
- ActiveGenie::Battle.basic(
69
- player_a,
70
- player_b,
71
- @criteria,
72
- config:
73
- ).values_at('winner', 'loser')
74
- end
75
-
76
- def update_elo(winner, loser)
77
- return if winner.nil? || loser.nil?
78
-
79
- new_winner_elo, new_loser_elo = calculate_new_elo(winner.elo, loser.elo)
80
-
81
- winner.elo = [new_winner_elo, max_defense_elo].min
82
- loser.elo = [new_loser_elo - LOSE_PENALTY, min_relegation_elo].max
83
- end
84
-
85
- def max_defense_elo
86
- @players.tier_defense.max_by(&:elo).elo
87
- end
88
-
89
- def min_relegation_elo
90
- @players.tier_relegation.min_by(&:elo).elo
91
- end
92
-
93
- # Read more about the formula on https://en.wikipedia.org/wiki/Elo_rating_system
94
- def calculate_new_elo(winner_elo, loser_elo)
95
- expected_score_a = 1 / (1 + 10**((loser_elo - winner_elo) / 400))
96
- expected_score_b = 1 - expected_score_a
97
-
98
- new_elo_winner = winner_elo + K * (1 - expected_score_a)
99
- new_elo_loser = loser_elo + K * (1 - expected_score_b)
100
-
101
- [new_elo_winner, new_elo_loser]
102
- end
103
-
104
- def eliminate_all_relegation_players
105
- eliminations = @players.tier_relegation.size
106
- @players.tier_relegation.each { |player| player.eliminated = 'tier_relegation' }
107
- ActiveGenie::Logger.trace({ **log, step: :elo_round, eligible_size: @players.eligible_size, eliminations: })
108
- end
109
-
110
- def config
111
- { **@config }
112
- end
113
-
114
- def log
115
- {
116
- **(@config.dig(:log) || {}),
117
- duration: Time.now - @start_time
118
- }
119
- end
120
- end
121
- end
@@ -1,62 +0,0 @@
1
- require_relative '../battle/basic'
2
-
3
- module ActiveGenie::Leaderboard
4
- class FreeForAll
5
- def self.call(players, criteria, config: {})
6
- new(players, criteria, config:).call
7
- end
8
-
9
- def initialize(players, criteria, config: {})
10
- @players = players
11
- @criteria = criteria
12
- @config = config
13
- @start_time = Time.now
14
- end
15
-
16
- def call
17
- matches.each do |player_a, player_b|
18
- winner, loser = battle(player_a, player_b)
19
-
20
- if winner.nil? || loser.nil?
21
- player_a.free_for_all[:draw] += 1
22
- player_b.free_for_all[:draw] += 1
23
- else
24
- winner.free_for_all[:win] += 1
25
- loser.free_for_all[:lose] += 1
26
- end
27
-
28
- ActiveGenie::Logger.trace({**log, step: :free_for_all_battle, winner_id: winner&.id, player_a_id: player_a.id, player_a_free_for_all_score: player_a.free_for_all_score, player_b_id: player_b.id, player_b_free_for_all_score: player_b.free_for_all_score })
29
- end
30
-
31
- @players
32
- end
33
-
34
- private
35
-
36
- # TODO: reduce the number of matches based on transitivity.
37
- # For example, if A is better than B, and B is better than C, then A should clearly be better than C
38
- def matches
39
- @players.eligible.combination(2).to_a
40
- end
41
-
42
- def battle(player_a, player_b)
43
- result = ActiveGenie::Battle.basic(
44
- player_a,
45
- player_b,
46
- @criteria,
47
- config:
48
- )
49
-
50
-
51
- result.values_at('winner', 'loser')
52
- end
53
-
54
- def config
55
- { **@config }
56
- end
57
-
58
- def log
59
- { **(@config.dig(:log) || {}), duration: Time.now - @start_time }
60
- end
61
- end
62
- end