sentiment_insights 0.2.0 → 0.4.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 +4 -4
- data/EXPORT_USAGE.md +325 -0
- data/Gemfile.lock +9 -1
- data/README.md +114 -18
- data/lib/sentiment_insights/clients/entities/claude_client.rb +131 -0
- data/lib/sentiment_insights/clients/key_phrases/claude_client.rb +151 -0
- data/lib/sentiment_insights/clients/sentiment/claude_client.rb +126 -0
- data/lib/sentiment_insights/configuration.rb +2 -1
- data/lib/sentiment_insights/export/base_exporter.rb +308 -0
- data/lib/sentiment_insights/export/csv_exporter.rb +261 -0
- data/lib/sentiment_insights/export/excel_exporter.rb +334 -0
- data/lib/sentiment_insights/export/exportable.rb +152 -0
- data/lib/sentiment_insights/export/exporter.rb +169 -0
- data/lib/sentiment_insights/export/json_exporter.rb +183 -0
- data/lib/sentiment_insights/insights/entities.rb +7 -2
- data/lib/sentiment_insights/insights/key_phrases.rb +6 -2
- data/lib/sentiment_insights/insights/sentiment.rb +7 -3
- data/lib/sentiment_insights/version.rb +1 -1
- data/lib/sentiment_insights.rb +1 -0
- data/sentiment_insights.gemspec +3 -0
- metadata +26 -2
@@ -0,0 +1,183 @@
|
|
1
|
+
require_relative 'base_exporter'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module SentimentInsights
|
5
|
+
module Export
|
6
|
+
class JsonExporter < BaseExporter
|
7
|
+
def export(filename = nil)
|
8
|
+
filename ||= generate_filename("json")
|
9
|
+
|
10
|
+
# Apply filters if specified
|
11
|
+
filtered_result = apply_filters(@result)
|
12
|
+
|
13
|
+
structured_data = build_json_structure(filtered_result)
|
14
|
+
|
15
|
+
if filename
|
16
|
+
File.write(filename, JSON.pretty_generate(structured_data))
|
17
|
+
filename
|
18
|
+
else
|
19
|
+
structured_data # Return data directly for API responses
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return JSON string without writing to file
|
24
|
+
def to_json_string
|
25
|
+
structured_data = build_json_structure(apply_filters(@result))
|
26
|
+
JSON.pretty_generate(structured_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return raw hash structure (for API responses)
|
30
|
+
def to_hash
|
31
|
+
build_json_structure(apply_filters(@result))
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_json_structure(data)
|
37
|
+
{
|
38
|
+
metadata: build_metadata,
|
39
|
+
analysis: build_analysis_data(data),
|
40
|
+
export_info: build_export_info
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_metadata
|
45
|
+
{
|
46
|
+
export_timestamp: Time.now.utc.iso8601,
|
47
|
+
analysis_type: analysis_type.to_s,
|
48
|
+
total_responses: extract_responses_data.count,
|
49
|
+
provider_used: detect_provider
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_analysis_data(data)
|
54
|
+
case analysis_type
|
55
|
+
when :sentiment
|
56
|
+
build_sentiment_json(data)
|
57
|
+
when :entities
|
58
|
+
build_entities_json(data)
|
59
|
+
when :key_phrases
|
60
|
+
build_phrases_json(data)
|
61
|
+
else
|
62
|
+
data
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_sentiment_json(data)
|
67
|
+
result = {
|
68
|
+
responses: format_responses_for_json(data[:responses] || [])
|
69
|
+
}
|
70
|
+
|
71
|
+
if options[:include_summary] && data[:global_summary]
|
72
|
+
result[:global_summary] = data[:global_summary]
|
73
|
+
end
|
74
|
+
|
75
|
+
if options[:include_segments] && data[:segment_summary]
|
76
|
+
result[:segment_summary] = format_segment_summary_for_json(data[:segment_summary])
|
77
|
+
end
|
78
|
+
|
79
|
+
if data[:top_positive_comments]
|
80
|
+
result[:top_positive_comments] = data[:top_positive_comments]
|
81
|
+
end
|
82
|
+
|
83
|
+
if data[:top_negative_comments]
|
84
|
+
result[:top_negative_comments] = data[:top_negative_comments]
|
85
|
+
end
|
86
|
+
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_entities_json(data)
|
91
|
+
{
|
92
|
+
entities: format_entities_for_json(data[:entities] || []),
|
93
|
+
responses: format_responses_for_json(data[:responses] || [])
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_phrases_json(data)
|
98
|
+
{
|
99
|
+
phrases: format_phrases_for_json(data[:phrases] || []),
|
100
|
+
responses: format_responses_for_json(data[:responses] || [])
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_responses_for_json(responses)
|
105
|
+
responses.map do |response|
|
106
|
+
formatted = {
|
107
|
+
id: response[:id] || "r_#{responses.index(response) + 1}",
|
108
|
+
text: response[:sentence] || response[:answer],
|
109
|
+
segment: response[:segment] || {}
|
110
|
+
}
|
111
|
+
|
112
|
+
# Add analysis-specific fields
|
113
|
+
case analysis_type
|
114
|
+
when :sentiment
|
115
|
+
formatted[:sentiment] = {
|
116
|
+
label: response[:sentiment_label] || response[:sentiment],
|
117
|
+
score: response[:sentiment_score] || 0.0
|
118
|
+
}
|
119
|
+
when :key_phrases
|
120
|
+
formatted[:sentiment] = {
|
121
|
+
label: response[:sentiment],
|
122
|
+
score: response[:sentiment_score] || 0.0
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
formatted[:timestamp] = format_timestamp if options[:include_timestamp]
|
127
|
+
formatted
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def format_entities_for_json(entities)
|
132
|
+
entities.map do |entity|
|
133
|
+
{
|
134
|
+
entity: entity[:entity],
|
135
|
+
type: entity[:type],
|
136
|
+
mentions: entity[:mentions] || [],
|
137
|
+
summary: entity[:summary] || {}
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def format_phrases_for_json(phrases)
|
143
|
+
phrases.map do |phrase|
|
144
|
+
{
|
145
|
+
phrase: phrase[:phrase],
|
146
|
+
mentions: phrase[:mentions] || [],
|
147
|
+
summary: phrase[:summary] || {}
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def format_segment_summary_for_json(segment_summary)
|
153
|
+
formatted = {}
|
154
|
+
segment_summary.each do |segment_type, segments|
|
155
|
+
next if [:top_positive_comments, :top_negative_comments].include?(segment_type)
|
156
|
+
|
157
|
+
formatted[segment_type] = {}
|
158
|
+
segments.each do |segment_value, stats|
|
159
|
+
formatted[segment_type][segment_value] = stats
|
160
|
+
end
|
161
|
+
end
|
162
|
+
formatted
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_export_info
|
166
|
+
{
|
167
|
+
format: "json",
|
168
|
+
options_used: @options,
|
169
|
+
filters_applied: @options[:filter] || {},
|
170
|
+
generated_at: Time.now.utc.iso8601
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
def detect_provider
|
175
|
+
# Try to detect which provider was used based on result structure
|
176
|
+
return "unknown" unless @result.is_a?(Hash)
|
177
|
+
|
178
|
+
# This could be enhanced based on provider-specific signatures
|
179
|
+
"auto-detected"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative '../export/exportable'
|
2
|
+
|
1
3
|
module SentimentInsights
|
2
4
|
module Insights
|
3
5
|
# Extracts and summarizes named entities from survey responses
|
@@ -9,6 +11,9 @@ module SentimentInsights
|
|
9
11
|
when :openai
|
10
12
|
require_relative '../clients/entities/open_ai_client'
|
11
13
|
Clients::Entities::OpenAIClient.new
|
14
|
+
when :claude
|
15
|
+
require_relative '../clients/entities/claude_client'
|
16
|
+
Clients::Entities::ClaudeClient.new
|
12
17
|
when :aws
|
13
18
|
require_relative '../clients/entities/aws_client'
|
14
19
|
Clients::Entities::AwsClient.new
|
@@ -55,10 +60,10 @@ module SentimentInsights
|
|
55
60
|
}
|
56
61
|
end
|
57
62
|
|
58
|
-
{
|
63
|
+
SentimentInsights::Export::Result.wrap({
|
59
64
|
entities: enriched_entities,
|
60
65
|
responses: responses
|
61
|
-
}
|
66
|
+
})
|
62
67
|
end
|
63
68
|
end
|
64
69
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative '../clients/key_phrases/open_ai_client'
|
2
|
+
require_relative '../clients/key_phrases/claude_client'
|
2
3
|
require_relative '../clients/key_phrases/aws_client'
|
4
|
+
require_relative '../export/exportable'
|
3
5
|
|
4
6
|
module SentimentInsights
|
5
7
|
module Insights
|
@@ -11,6 +13,8 @@ module SentimentInsights
|
|
11
13
|
@provider_client = provider_client || case effective_provider
|
12
14
|
when :openai
|
13
15
|
Clients::KeyPhrases::OpenAIClient.new
|
16
|
+
when :claude
|
17
|
+
Clients::KeyPhrases::ClaudeClient.new
|
14
18
|
when :aws
|
15
19
|
Clients::KeyPhrases::AwsClient.new
|
16
20
|
when :sentimental
|
@@ -70,10 +74,10 @@ module SentimentInsights
|
|
70
74
|
}
|
71
75
|
end
|
72
76
|
|
73
|
-
{
|
77
|
+
SentimentInsights::Export::Result.wrap({
|
74
78
|
phrases: enriched_phrases,
|
75
79
|
responses: responses
|
76
|
-
}
|
80
|
+
})
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require_relative '../clients/sentiment/open_ai_client'
|
2
|
+
require_relative '../clients/sentiment/claude_client'
|
2
3
|
require_relative '../clients/sentiment/sentimental_client'
|
3
4
|
require_relative '../clients/sentiment/aws_comprehend_client'
|
5
|
+
require_relative '../export/exportable'
|
4
6
|
|
5
7
|
module SentimentInsights
|
6
8
|
module Insights
|
@@ -15,6 +17,8 @@ module SentimentInsights
|
|
15
17
|
@provider_client = provider_client || case effective_provider
|
16
18
|
when :openai
|
17
19
|
Clients::Sentiment::OpenAIClient.new
|
20
|
+
when :claude
|
21
|
+
Clients::Sentiment::ClaudeClient.new
|
18
22
|
when :aws
|
19
23
|
Clients::Sentiment::AwsComprehendClient.new
|
20
24
|
else
|
@@ -49,14 +53,14 @@ module SentimentInsights
|
|
49
53
|
|
50
54
|
top_positive_comments, top_negative_comments = top_comments(annotated_responses)
|
51
55
|
|
52
|
-
# Assemble the result hash
|
53
|
-
{
|
56
|
+
# Assemble the result hash and wrap with export functionality
|
57
|
+
SentimentInsights::Export::Result.wrap({
|
54
58
|
global_summary: global_summary,
|
55
59
|
segment_summary: segment_summary,
|
56
60
|
top_positive_comments: top_positive_comments,
|
57
61
|
top_negative_comments: top_negative_comments,
|
58
62
|
responses: annotated_responses
|
59
|
-
}
|
63
|
+
})
|
60
64
|
end
|
61
65
|
|
62
66
|
private
|
data/lib/sentiment_insights.rb
CHANGED
data/sentiment_insights.gemspec
CHANGED
@@ -31,6 +31,9 @@ Gem::Specification.new do |spec|
|
|
31
31
|
# Runtime dependencies
|
32
32
|
spec.add_dependency "sentimental", "~> 1.4.0"
|
33
33
|
spec.add_dependency "aws-sdk-comprehend", ">= 1.98.0"
|
34
|
+
|
35
|
+
# Export dependencies
|
36
|
+
spec.add_dependency "rubyXL", "~> 3.4" # Excel export support
|
34
37
|
|
35
38
|
# Development dependencies
|
36
39
|
spec.add_development_dependency "bundler", "~> 2.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentiment_insights
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mathrailsAI
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sentimental
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.98.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubyXL
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,6 +120,7 @@ files:
|
|
106
120
|
- ".gitignore"
|
107
121
|
- ".rspec"
|
108
122
|
- CODE_OF_CONDUCT.md
|
123
|
+
- EXPORT_USAGE.md
|
109
124
|
- Gemfile
|
110
125
|
- Gemfile.lock
|
111
126
|
- LICENSE
|
@@ -117,13 +132,22 @@ files:
|
|
117
132
|
- lib/sentiment_insights.rb
|
118
133
|
- lib/sentiment_insights/analyzer.rb
|
119
134
|
- lib/sentiment_insights/clients/entities/aws_client.rb
|
135
|
+
- lib/sentiment_insights/clients/entities/claude_client.rb
|
120
136
|
- lib/sentiment_insights/clients/entities/open_ai_client.rb
|
121
137
|
- lib/sentiment_insights/clients/key_phrases/aws_client.rb
|
138
|
+
- lib/sentiment_insights/clients/key_phrases/claude_client.rb
|
122
139
|
- lib/sentiment_insights/clients/key_phrases/open_ai_client.rb
|
123
140
|
- lib/sentiment_insights/clients/sentiment/aws_comprehend_client.rb
|
141
|
+
- lib/sentiment_insights/clients/sentiment/claude_client.rb
|
124
142
|
- lib/sentiment_insights/clients/sentiment/open_ai_client.rb
|
125
143
|
- lib/sentiment_insights/clients/sentiment/sentimental_client.rb
|
126
144
|
- lib/sentiment_insights/configuration.rb
|
145
|
+
- lib/sentiment_insights/export/base_exporter.rb
|
146
|
+
- lib/sentiment_insights/export/csv_exporter.rb
|
147
|
+
- lib/sentiment_insights/export/excel_exporter.rb
|
148
|
+
- lib/sentiment_insights/export/exportable.rb
|
149
|
+
- lib/sentiment_insights/export/exporter.rb
|
150
|
+
- lib/sentiment_insights/export/json_exporter.rb
|
127
151
|
- lib/sentiment_insights/insights/entities.rb
|
128
152
|
- lib/sentiment_insights/insights/key_phrases.rb
|
129
153
|
- lib/sentiment_insights/insights/sentiment.rb
|