sentiment_insights 0.3.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
@@ -58,10 +60,10 @@ module SentimentInsights
58
60
  }
59
61
  end
60
62
 
61
- {
63
+ SentimentInsights::Export::Result.wrap({
62
64
  entities: enriched_entities,
63
65
  responses: responses
64
- }
66
+ })
65
67
  end
66
68
  end
67
69
  end
@@ -1,6 +1,7 @@
1
1
  require_relative '../clients/key_phrases/open_ai_client'
2
2
  require_relative '../clients/key_phrases/claude_client'
3
3
  require_relative '../clients/key_phrases/aws_client'
4
+ require_relative '../export/exportable'
4
5
 
5
6
  module SentimentInsights
6
7
  module Insights
@@ -73,10 +74,10 @@ module SentimentInsights
73
74
  }
74
75
  end
75
76
 
76
- {
77
+ SentimentInsights::Export::Result.wrap({
77
78
  phrases: enriched_phrases,
78
79
  responses: responses
79
- }
80
+ })
80
81
  end
81
82
  end
82
83
  end
@@ -2,6 +2,7 @@ require_relative '../clients/sentiment/open_ai_client'
2
2
  require_relative '../clients/sentiment/claude_client'
3
3
  require_relative '../clients/sentiment/sentimental_client'
4
4
  require_relative '../clients/sentiment/aws_comprehend_client'
5
+ require_relative '../export/exportable'
5
6
 
6
7
  module SentimentInsights
7
8
  module Insights
@@ -52,14 +53,14 @@ module SentimentInsights
52
53
 
53
54
  top_positive_comments, top_negative_comments = top_comments(annotated_responses)
54
55
 
55
- # Assemble the result hash
56
- {
56
+ # Assemble the result hash and wrap with export functionality
57
+ SentimentInsights::Export::Result.wrap({
57
58
  global_summary: global_summary,
58
59
  segment_summary: segment_summary,
59
60
  top_positive_comments: top_positive_comments,
60
61
  top_negative_comments: top_negative_comments,
61
62
  responses: annotated_responses
62
- }
63
+ })
63
64
  end
64
65
 
65
66
  private
@@ -1,3 +1,3 @@
1
1
  module SentimentInsights
2
- VERSION = "0.3.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.3.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-06-25 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
@@ -127,6 +142,12 @@ files:
127
142
  - lib/sentiment_insights/clients/sentiment/open_ai_client.rb
128
143
  - lib/sentiment_insights/clients/sentiment/sentimental_client.rb
129
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
130
151
  - lib/sentiment_insights/insights/entities.rb
131
152
  - lib/sentiment_insights/insights/key_phrases.rb
132
153
  - lib/sentiment_insights/insights/sentiment.rb