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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module SentimentInsights
2
- VERSION = "0.2.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require "sentiment_insights/configuration"
2
2
  require "sentiment_insights/analyzer"
3
3
  require "sentiment_insights/insights/sentiment"
4
+ require "sentiment_insights/export/exportable"
4
5
 
5
6
  module SentimentInsights
6
7
  class Error < StandardError; end
@@ -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.2.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-05-26 00:00:00.000000000 Z
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