sentiment_insights 0.1.0
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +57 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +265 -0
- data/Rakefile +6 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/sentiment_insights/analyzer.rb +48 -0
- data/lib/sentiment_insights/clients/entities/aws_client.rb +84 -0
- data/lib/sentiment_insights/clients/entities/open_ai_client.rb +113 -0
- data/lib/sentiment_insights/clients/key_phrases/aws_client.rb +89 -0
- data/lib/sentiment_insights/clients/key_phrases/open_ai_client.rb +119 -0
- data/lib/sentiment_insights/clients/sentiment/aws_comprehend_client.rb +72 -0
- data/lib/sentiment_insights/clients/sentiment/open_ai_client.rb +115 -0
- data/lib/sentiment_insights/clients/sentiment/sentimental_client.rb +33 -0
- data/lib/sentiment_insights/configuration.rb +11 -0
- data/lib/sentiment_insights/insights/entities.rb +66 -0
- data/lib/sentiment_insights/insights/key_phrases.rb +80 -0
- data/lib/sentiment_insights/insights/sentiment.rb +152 -0
- data/lib/sentiment_insights/insights/topics.rb +0 -0
- data/lib/sentiment_insights/version.rb +3 -0
- data/lib/sentiment_insights.rb +15 -0
- data/sentiment_insights.gemspec +42 -0
- metadata +159 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative '../clients/key_phrases/open_ai_client'
|
2
|
+
require_relative '../clients/key_phrases/aws_client'
|
3
|
+
|
4
|
+
module SentimentInsights
|
5
|
+
module Insights
|
6
|
+
# Extracts and summarizes key phrases from survey responses
|
7
|
+
class KeyPhrases
|
8
|
+
def initialize(provider: nil, provider_client: nil)
|
9
|
+
effective_provider = provider || SentimentInsights.configuration&.provider || :sentimental
|
10
|
+
|
11
|
+
@provider_client = provider_client || case effective_provider
|
12
|
+
when :openai
|
13
|
+
Clients::KeyPhrases::OpenAIClient.new
|
14
|
+
when :aws
|
15
|
+
Clients::KeyPhrases::AwsClient.new
|
16
|
+
when :sentimental
|
17
|
+
raise NotImplementedError, "Key phrase extraction is not supported for the 'sentimental' provider"
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Unsupported provider: #{effective_provider}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Extract key phrases and build a normalized, summarized output
|
24
|
+
# @param entries [Array<Hash>] each with :answer and optional :segment
|
25
|
+
# @param question [String, nil] optional context
|
26
|
+
# @return [Hash] { phrases: [...], responses: [...] }
|
27
|
+
def extract(entries, question: nil)
|
28
|
+
entries = entries.to_a
|
29
|
+
raw_result = @provider_client.extract_batch(entries, question: question)
|
30
|
+
|
31
|
+
responses = raw_result[:responses] || []
|
32
|
+
phrases = raw_result[:phrases] || []
|
33
|
+
puts "phrases = #{phrases}"
|
34
|
+
|
35
|
+
puts "responses = #{responses}"
|
36
|
+
# Index responses by id for lookup
|
37
|
+
response_index = {}
|
38
|
+
responses.each do |r|
|
39
|
+
response_index[r[:id]] = r
|
40
|
+
end
|
41
|
+
|
42
|
+
enriched_phrases = phrases.map do |phrase_entry|
|
43
|
+
mentions = phrase_entry[:mentions] || []
|
44
|
+
mention_responses = mentions.map { |id| response_index[id] }.compact
|
45
|
+
|
46
|
+
sentiment_dist = Hash.new(0)
|
47
|
+
segment_dist = Hash.new { |h, k| h[k] = Hash.new(0) }
|
48
|
+
|
49
|
+
mention_responses.each do |resp|
|
50
|
+
sentiment = resp[:sentiment] || :neutral
|
51
|
+
sentiment_dist[sentiment] += 1
|
52
|
+
|
53
|
+
(resp[:segment] || {}).each do |seg_key, seg_val|
|
54
|
+
segment_dist[seg_key][seg_val] += 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
{
|
59
|
+
phrase: phrase_entry[:phrase],
|
60
|
+
mentions: mentions,
|
61
|
+
summary: {
|
62
|
+
total_mentions: mentions.size,
|
63
|
+
sentiment_distribution: {
|
64
|
+
positive: sentiment_dist[:positive],
|
65
|
+
negative: sentiment_dist[:negative],
|
66
|
+
neutral: sentiment_dist[:neutral]
|
67
|
+
},
|
68
|
+
segment_distribution: segment_dist
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
{
|
74
|
+
phrases: enriched_phrases,
|
75
|
+
responses: responses
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require_relative '../clients/sentiment/open_ai_client'
|
2
|
+
require_relative '../clients/sentiment/sentimental_client'
|
3
|
+
|
4
|
+
module SentimentInsights
|
5
|
+
module Insights
|
6
|
+
# Analyzes sentiment of survey responses and produces summarized insights.
|
7
|
+
class Sentiment
|
8
|
+
DEFAULT_TOP_COUNT = 5
|
9
|
+
|
10
|
+
# Initialize with a specified provider or a concrete provider client.
|
11
|
+
# If no provider is given, default to the Sentimental (local) provider.
|
12
|
+
def initialize(provider: nil, provider_client: nil, top_count: DEFAULT_TOP_COUNT)
|
13
|
+
effective_provider = provider || SentimentInsights.configuration&.provider || :sentimental
|
14
|
+
@provider_client = provider_client || case effective_provider
|
15
|
+
when :openai
|
16
|
+
Clients::Sentiment::OpenAIClient.new
|
17
|
+
when :aws
|
18
|
+
require_relative '../clients/sentiment/aws_comprehend_client'
|
19
|
+
Clients::Sentiment::AwsComprehendClient.new
|
20
|
+
else
|
21
|
+
Clients::Sentiment::SentimentalClient.new
|
22
|
+
end
|
23
|
+
@top_count = top_count
|
24
|
+
end
|
25
|
+
|
26
|
+
# Analyze a batch of entries and return sentiment insights.
|
27
|
+
# @param entries [Array<Hash>] An array of response hashes, each with :answer and :segment.
|
28
|
+
# @param question [String, nil] Optional global question text or metadata for context.
|
29
|
+
# @return [Hash] Summary of sentiment analysis (global, segment-wise, top comments, and annotated responses).
|
30
|
+
def analyze(entries, question: nil)
|
31
|
+
# Ensure entries is an array of hashes with required keys
|
32
|
+
entries = entries.to_a
|
33
|
+
# Get sentiment results for each entry from the provider client
|
34
|
+
results = @provider_client.analyze_entries(entries, question: question)
|
35
|
+
|
36
|
+
# Combine original entries with sentiment results
|
37
|
+
annotated_responses = entries.each_with_index.map do |entry, idx|
|
38
|
+
res = results[idx] || {}
|
39
|
+
{
|
40
|
+
answer: entry[:answer],
|
41
|
+
segment: entry[:segment] || {},
|
42
|
+
sentiment_label: res[:label],
|
43
|
+
sentiment_score: res[:score]
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
global_summary = prepare_global_summary(annotated_responses)
|
48
|
+
segment_summary = prepare_segment_summary(annotated_responses)
|
49
|
+
|
50
|
+
top_positive_comments, top_negative_comments = top_comments(annotated_responses)
|
51
|
+
|
52
|
+
# Assemble the result hash
|
53
|
+
{
|
54
|
+
global_summary: global_summary,
|
55
|
+
segment_summary: segment_summary,
|
56
|
+
top_positive_comments: top_positive_comments,
|
57
|
+
top_negative_comments: top_negative_comments,
|
58
|
+
responses: annotated_responses
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def prepare_global_summary(annotated_responses)
|
65
|
+
# Global sentiment counts
|
66
|
+
total_count = annotated_responses.size
|
67
|
+
positive_count = annotated_responses.count { |r| r[:sentiment_label] == :positive }
|
68
|
+
negative_count = annotated_responses.count { |r| r[:sentiment_label] == :negative }
|
69
|
+
neutral_count = annotated_responses.count { |r| r[:sentiment_label] == :neutral }
|
70
|
+
|
71
|
+
# Global percentages (avoid division by zero)
|
72
|
+
positive_pct = total_count > 0 ? (positive_count.to_f * 100.0 / total_count) : 0.0
|
73
|
+
negative_pct = total_count > 0 ? (negative_count.to_f * 100.0 / total_count) : 0.0
|
74
|
+
neutral_pct = total_count > 0 ? (neutral_count.to_f * 100.0 / total_count) : 0.0
|
75
|
+
|
76
|
+
# Net sentiment score = positive% - negative%
|
77
|
+
net_sentiment = positive_pct - negative_pct
|
78
|
+
|
79
|
+
{
|
80
|
+
total_count: total_count,
|
81
|
+
positive_count: positive_count,
|
82
|
+
neutral_count: neutral_count,
|
83
|
+
negative_count: negative_count,
|
84
|
+
positive_percentage: positive_pct,
|
85
|
+
neutral_percentage: neutral_pct,
|
86
|
+
negative_percentage: negative_pct,
|
87
|
+
net_sentiment_score: net_sentiment
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def prepare_segment_summary(annotated_responses)
|
92
|
+
# Per-segment sentiment summary (for each segment attribute and value)
|
93
|
+
segment_summary = {}
|
94
|
+
annotated_responses.each do |resp|
|
95
|
+
resp[:segment].each do |seg_key, seg_val|
|
96
|
+
segment_summary[seg_key] ||= {}
|
97
|
+
segment_summary[seg_key][seg_val] ||= {
|
98
|
+
total_count: 0,
|
99
|
+
positive_count: 0,
|
100
|
+
neutral_count: 0,
|
101
|
+
negative_count: 0,
|
102
|
+
positive_percentage: 0.0,
|
103
|
+
neutral_percentage: 0.0,
|
104
|
+
negative_percentage: 0.0,
|
105
|
+
net_sentiment_score: 0.0
|
106
|
+
}
|
107
|
+
group = segment_summary[seg_key][seg_val]
|
108
|
+
# Increment counts per sentiment
|
109
|
+
group[:total_count] += 1
|
110
|
+
case resp[:sentiment_label]
|
111
|
+
when :positive then group[:positive_count] += 1
|
112
|
+
when :neutral then group[:neutral_count] += 1
|
113
|
+
when :negative then group[:negative_count] += 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Compute percentages and net sentiment for each segment group
|
119
|
+
segment_summary.each do |_, groups|
|
120
|
+
groups.each do |_, stats|
|
121
|
+
total = stats[:total_count]
|
122
|
+
if total > 0
|
123
|
+
stats[:positive_percentage] = (stats[:positive_count].to_f * 100.0 / total)
|
124
|
+
stats[:neutral_percentage] = (stats[:neutral_count].to_f * 100.0 / total)
|
125
|
+
stats[:negative_percentage] = (stats[:negative_count].to_f * 100.0 / total)
|
126
|
+
stats[:net_sentiment_score] = stats[:positive_percentage] - stats[:negative_percentage]
|
127
|
+
else
|
128
|
+
stats[:positive_percentage] = stats[:neutral_percentage] = stats[:negative_percentage] = 0.0
|
129
|
+
stats[:net_sentiment_score] = 0.0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Identify top N positive and negative responses by score
|
136
|
+
def top_comments(annotated_responses)
|
137
|
+
top_positive = annotated_responses.select { |r| r[:sentiment_label] == :positive }
|
138
|
+
top_positive.sort_by! { |r| -r[:sentiment_score].to_f } # descending by score (if all 1.0, order remains as is)
|
139
|
+
top_negative = annotated_responses.select { |r| r[:sentiment_label] == :negative }
|
140
|
+
top_negative.sort_by! { |r| r[:sentiment_score].to_f } # ascending by score (more negative first, since score is -1.0)
|
141
|
+
|
142
|
+
top_positive_comments = top_positive.first(@top_count).map do |r|
|
143
|
+
{ answer: r[:answer], score: r[:sentiment_score] }
|
144
|
+
end
|
145
|
+
top_negative_comments = top_negative.first(@top_count).map do |r|
|
146
|
+
{ answer: r[:answer], score: r[:sentiment_score] }
|
147
|
+
end
|
148
|
+
[top_positive_comments, top_negative_comments]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "sentiment_insights/configuration"
|
2
|
+
require "sentiment_insights/analyzer"
|
3
|
+
require "sentiment_insights/insights/sentiment"
|
4
|
+
|
5
|
+
module SentimentInsights
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
def self.configure
|
9
|
+
yield(configuration)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "sentiment_insights/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "sentiment_insights"
|
7
|
+
spec.version = SentimentInsights::VERSION
|
8
|
+
spec.authors = ["mathrailsAI"]
|
9
|
+
spec.email = ["mathrails@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Analyze and extract sentiment insights from text data easily."
|
12
|
+
spec.description = "SentimentInsights is a Ruby gem that helps analyze sentiment from survey responses, feedback, and other text sources. Built for developers who need quick and actionable sentiment extraction."
|
13
|
+
|
14
|
+
spec.homepage = "https://github.com/mathrailsAI/sentiment_insights"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/mathrailsAI/sentiment_insights"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/mathrailsAI/sentiment_insights/blob/main/CHANGELOG.md"
|
21
|
+
# Removed allowed_push_host — usually not needed unless you have a private server
|
22
|
+
else
|
23
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Runtime dependencies
|
34
|
+
spec.add_dependency "sentimental", "~> 1.4.0"
|
35
|
+
spec.add_dependency "aws-sdk-comprehend", "~> 1.98.0"
|
36
|
+
|
37
|
+
# Development dependencies
|
38
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
39
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
40
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
41
|
+
spec.add_development_dependency "dotenv", "~> 2.8"
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sentiment_insights
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- mathrailsAI
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sentimental
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.4.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk-comprehend
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.98.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.98.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dotenv
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.8'
|
97
|
+
description: SentimentInsights is a Ruby gem that helps analyze sentiment from survey
|
98
|
+
responses, feedback, and other text sources. Built for developers who need quick
|
99
|
+
and actionable sentiment extraction.
|
100
|
+
email:
|
101
|
+
- mathrails@gmail.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rspec"
|
108
|
+
- CODE_OF_CONDUCT.md
|
109
|
+
- Gemfile
|
110
|
+
- Gemfile.lock
|
111
|
+
- LICENSE
|
112
|
+
- LICENSE.txt
|
113
|
+
- README.md
|
114
|
+
- Rakefile
|
115
|
+
- bin/console
|
116
|
+
- bin/setup
|
117
|
+
- lib/sentiment_insights.rb
|
118
|
+
- lib/sentiment_insights/analyzer.rb
|
119
|
+
- lib/sentiment_insights/clients/entities/aws_client.rb
|
120
|
+
- lib/sentiment_insights/clients/entities/open_ai_client.rb
|
121
|
+
- lib/sentiment_insights/clients/key_phrases/aws_client.rb
|
122
|
+
- lib/sentiment_insights/clients/key_phrases/open_ai_client.rb
|
123
|
+
- lib/sentiment_insights/clients/sentiment/aws_comprehend_client.rb
|
124
|
+
- lib/sentiment_insights/clients/sentiment/open_ai_client.rb
|
125
|
+
- lib/sentiment_insights/clients/sentiment/sentimental_client.rb
|
126
|
+
- lib/sentiment_insights/configuration.rb
|
127
|
+
- lib/sentiment_insights/insights/entities.rb
|
128
|
+
- lib/sentiment_insights/insights/key_phrases.rb
|
129
|
+
- lib/sentiment_insights/insights/sentiment.rb
|
130
|
+
- lib/sentiment_insights/insights/topics.rb
|
131
|
+
- lib/sentiment_insights/version.rb
|
132
|
+
- sentiment_insights.gemspec
|
133
|
+
homepage: https://github.com/mathrailsAI/sentiment_insights
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata:
|
137
|
+
homepage_uri: https://github.com/mathrailsAI/sentiment_insights
|
138
|
+
source_code_uri: https://github.com/mathrailsAI/sentiment_insights
|
139
|
+
changelog_uri: https://github.com/mathrailsAI/sentiment_insights/blob/main/CHANGELOG.md
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubygems_version: 3.1.6
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Analyze and extract sentiment insights from text data easily.
|
159
|
+
test_files: []
|