myavana-bot-test-core 2.0.6 → 2.0.7

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,305 @@
1
+ -- Migration 003: Sienna Naturals Enhancements and Optimizations
2
+ -- Extends the database schema with Sienna Naturals specific tables and optimizations
3
+ -- Run this after 001_add_analytics_tables.sql and 002_add_advanced_features_tables.sql
4
+
5
+ -- Sienna Naturals specific brand tracking
6
+ CREATE TABLE IF NOT EXISTS brand_interactions (
7
+ id SERIAL PRIMARY KEY,
8
+ user_id VARCHAR(255) NOT NULL,
9
+ conversation_id VARCHAR(255),
10
+ brand_name VARCHAR(100) NOT NULL, -- 'sienna_naturals', 'myavana', etc.
11
+ interaction_type VARCHAR(100), -- product_inquiry, store_locator, discount_request
12
+ interaction_data JSONB,
13
+ user_satisfaction INTEGER,
14
+ created_at TIMESTAMP DEFAULT NOW()
15
+ );
16
+
17
+ -- Store locator requests (Sienna Naturals specific)
18
+ CREATE TABLE IF NOT EXISTS store_locator_requests (
19
+ id SERIAL PRIMARY KEY,
20
+ user_id VARCHAR(255) NOT NULL,
21
+ location_query VARCHAR(255),
22
+ user_location JSONB, -- {city, state, zip, coordinates if available}
23
+ stores_found INTEGER DEFAULT 0,
24
+ stores_data JSONB, -- array of store information returned
25
+ created_at TIMESTAMP DEFAULT NOW()
26
+ );
27
+
28
+ -- Product availability tracking by brand
29
+ CREATE TABLE IF NOT EXISTS product_availability_tracking (
30
+ id SERIAL PRIMARY KEY,
31
+ brand_name VARCHAR(100) NOT NULL,
32
+ product_name VARCHAR(255) NOT NULL,
33
+ availability_status VARCHAR(50) DEFAULT 'available', -- available, out_of_stock, discontinued
34
+ inventory_level VARCHAR(50), -- high, medium, low
35
+ last_checked TIMESTAMP DEFAULT NOW(),
36
+ notifications_sent INTEGER DEFAULT 0,
37
+ UNIQUE(brand_name, product_name)
38
+ );
39
+
40
+ -- Enhanced circuit breaker states for AI models
41
+ CREATE TABLE IF NOT EXISTS circuit_breaker_states (
42
+ id SERIAL PRIMARY KEY,
43
+ model_name VARCHAR(100) NOT NULL UNIQUE,
44
+ state VARCHAR(20) DEFAULT 'closed', -- closed, open, half_open
45
+ failure_count INTEGER DEFAULT 0,
46
+ last_failure_time TIMESTAMP,
47
+ last_success_time TIMESTAMP,
48
+ threshold_failures INTEGER DEFAULT 5,
49
+ timeout_duration INTERVAL DEFAULT '5 minutes',
50
+ updated_at TIMESTAMP DEFAULT NOW()
51
+ );
52
+
53
+ -- Response caching with brand-specific keys
54
+ CREATE TABLE IF NOT EXISTS response_cache (
55
+ id SERIAL PRIMARY KEY,
56
+ cache_key VARCHAR(512) NOT NULL UNIQUE, -- hash of query + user context + brand
57
+ brand_name VARCHAR(100),
58
+ cached_response JSONB NOT NULL,
59
+ user_context_hash VARCHAR(64), -- hash of user profile for personalization
60
+ query_hash VARCHAR(64), -- hash of the query
61
+ context_analysis JSONB, -- context analysis data
62
+ hit_count INTEGER DEFAULT 0,
63
+ created_at TIMESTAMP DEFAULT NOW(),
64
+ expires_at TIMESTAMP DEFAULT NOW() + INTERVAL '24 hours'
65
+ );
66
+
67
+ -- Smart prompt analytics
68
+ CREATE TABLE IF NOT EXISTS smart_prompt_analytics (
69
+ id SERIAL PRIMARY KEY,
70
+ user_id VARCHAR(255) NOT NULL,
71
+ conversation_id VARCHAR(255),
72
+ brand_name VARCHAR(100),
73
+ query_analysis JSONB, -- topics, intent, complexity
74
+ prompt_strategy VARCHAR(100), -- educational, sales, support, etc.
75
+ context_elements_used JSONB, -- faqs, products, previous_conversations
76
+ prompt_length INTEGER,
77
+ response_quality_score FLOAT, -- 0-1 based on user engagement
78
+ created_at TIMESTAMP DEFAULT NOW()
79
+ );
80
+
81
+ -- User engagement patterns
82
+ CREATE TABLE IF NOT EXISTS user_engagement_patterns (
83
+ id SERIAL PRIMARY KEY,
84
+ user_id VARCHAR(255) NOT NULL UNIQUE,
85
+ brand_name VARCHAR(100),
86
+ total_interactions INTEGER DEFAULT 0,
87
+ avg_session_duration INTERVAL,
88
+ preferred_interaction_time TIME,
89
+ preferred_day_of_week INTEGER, -- 0-6, 0=Sunday
90
+ response_style_preference VARCHAR(100), -- detailed, concise, visual, etc.
91
+ topic_preferences JSONB, -- array of preferred topics
92
+ engagement_score FLOAT DEFAULT 0, -- 0-1 based on overall engagement
93
+ last_interaction TIMESTAMP,
94
+ updated_at TIMESTAMP DEFAULT NOW()
95
+ );
96
+
97
+ -- Model performance benchmarks
98
+ CREATE TABLE IF NOT EXISTS model_performance_benchmarks (
99
+ id SERIAL PRIMARY KEY,
100
+ model_name VARCHAR(100) NOT NULL,
101
+ brand_name VARCHAR(100),
102
+ benchmark_type VARCHAR(100), -- response_time, accuracy, user_satisfaction
103
+ benchmark_value FLOAT,
104
+ sample_size INTEGER,
105
+ measurement_date DATE DEFAULT CURRENT_DATE,
106
+ measurement_context JSONB, -- conditions during measurement
107
+ UNIQUE(model_name, brand_name, benchmark_type, measurement_date)
108
+ );
109
+
110
+ -- Advanced indexes for performance
111
+ CREATE INDEX IF NOT EXISTS idx_brand_interactions_user_brand ON brand_interactions(user_id, brand_name, created_at);
112
+ CREATE INDEX IF NOT EXISTS idx_store_locator_location ON store_locator_requests(user_location);
113
+ CREATE INDEX IF NOT EXISTS idx_response_cache_key ON response_cache(cache_key, expires_at) WHERE expires_at > NOW();
114
+ CREATE INDEX IF NOT EXISTS idx_response_cache_brand_context ON response_cache(brand_name, user_context_hash) WHERE expires_at > NOW();
115
+ CREATE INDEX IF NOT EXISTS idx_smart_prompt_user_brand ON smart_prompt_analytics(user_id, brand_name, created_at);
116
+ CREATE INDEX IF NOT EXISTS idx_user_engagement_brand ON user_engagement_patterns(brand_name, engagement_score DESC);
117
+ CREATE INDEX IF NOT EXISTS idx_circuit_breaker_model ON circuit_breaker_states(model_name, state);
118
+
119
+ -- Composite indexes for common query patterns
120
+ CREATE INDEX IF NOT EXISTS idx_conversation_metrics_composite ON conversation_metrics(user_id, ai_model_used, created_at);
121
+ CREATE INDEX IF NOT EXISTS idx_user_journey_composite ON user_journey_events(user_id, event_type, hair_journey_stage, created_at);
122
+ CREATE INDEX IF NOT EXISTS idx_error_logs_composite ON error_logs(model_name, error_type, recovery_successful, created_at);
123
+
124
+ -- Partitioning preparation (for high-volume tables)
125
+ -- Note: These are preparation steps for future partitioning
126
+ CREATE INDEX IF NOT EXISTS idx_conversation_metrics_created_monthly ON conversation_metrics(DATE_TRUNC('month', created_at));
127
+ CREATE INDEX IF NOT EXISTS idx_user_journey_events_created_monthly ON user_journey_events(DATE_TRUNC('month', created_at));
128
+ CREATE INDEX IF NOT EXISTS idx_error_logs_created_monthly ON error_logs(DATE_TRUNC('month', created_at));
129
+
130
+ -- Views for Sienna Naturals specific analytics
131
+ CREATE OR REPLACE VIEW sienna_naturals_user_stats AS
132
+ SELECT
133
+ DATE(created_at) as date,
134
+ COUNT(DISTINCT user_id) as unique_users,
135
+ COUNT(*) as total_interactions,
136
+ COUNT(CASE WHEN interaction_type = 'product_inquiry' THEN 1 END) as product_inquiries,
137
+ COUNT(CASE WHEN interaction_type = 'store_locator' THEN 1 END) as store_locator_requests,
138
+ COUNT(CASE WHEN interaction_type = 'discount_request' THEN 1 END) as discount_requests,
139
+ AVG(user_satisfaction) as avg_satisfaction
140
+ FROM brand_interactions
141
+ WHERE brand_name = 'sienna_naturals'
142
+ GROUP BY DATE(created_at)
143
+ ORDER BY date DESC;
144
+
145
+ CREATE OR REPLACE VIEW brand_performance_comparison AS
146
+ SELECT
147
+ brand_name,
148
+ COUNT(DISTINCT user_id) as unique_users,
149
+ COUNT(*) as total_interactions,
150
+ AVG(user_satisfaction) as avg_satisfaction,
151
+ COUNT(CASE WHEN user_satisfaction >= 4 THEN 1 END) as satisfied_interactions,
152
+ ROUND(
153
+ COUNT(CASE WHEN user_satisfaction >= 4 THEN 1 END)::float /
154
+ COUNT(CASE WHEN user_satisfaction IS NOT NULL THEN 1 END) * 100, 2
155
+ ) as satisfaction_rate
156
+ FROM brand_interactions
157
+ WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
158
+ GROUP BY brand_name
159
+ ORDER BY satisfaction_rate DESC;
160
+
161
+ CREATE OR REPLACE VIEW cache_performance_stats AS
162
+ SELECT
163
+ brand_name,
164
+ COUNT(*) as total_cached_responses,
165
+ SUM(hit_count) as total_cache_hits,
166
+ COUNT(CASE WHEN expires_at > NOW() THEN 1 END) as active_cache_entries,
167
+ AVG(hit_count) as avg_hits_per_entry,
168
+ ROUND(
169
+ COUNT(CASE WHEN hit_count > 0 THEN 1 END)::float /
170
+ COUNT(*) * 100, 2
171
+ ) as cache_utilization_rate
172
+ FROM response_cache
173
+ GROUP BY brand_name
174
+ ORDER BY cache_utilization_rate DESC;
175
+
176
+ CREATE OR REPLACE VIEW model_circuit_breaker_status AS
177
+ SELECT
178
+ model_name,
179
+ state,
180
+ failure_count,
181
+ last_failure_time,
182
+ last_success_time,
183
+ CASE
184
+ WHEN state = 'open' AND last_failure_time + timeout_duration < NOW() THEN 'ready_for_half_open'
185
+ WHEN state = 'closed' AND failure_count >= threshold_failures THEN 'approaching_threshold'
186
+ ELSE 'normal'
187
+ END as status_indicator
188
+ FROM circuit_breaker_states
189
+ ORDER BY
190
+ CASE state
191
+ WHEN 'open' THEN 1
192
+ WHEN 'half_open' THEN 2
193
+ WHEN 'closed' THEN 3
194
+ END,
195
+ failure_count DESC;
196
+
197
+ -- Functions for automatic maintenance
198
+ CREATE OR REPLACE FUNCTION cleanup_expired_cache()
199
+ RETURNS INTEGER AS $$
200
+ DECLARE
201
+ deleted_count INTEGER;
202
+ BEGIN
203
+ DELETE FROM response_cache WHERE expires_at < NOW();
204
+ GET DIAGNOSTICS deleted_count = ROW_COUNT;
205
+ RETURN deleted_count;
206
+ END;
207
+ $$ LANGUAGE plpgsql;
208
+
209
+ CREATE OR REPLACE FUNCTION update_engagement_patterns()
210
+ RETURNS VOID AS $$
211
+ BEGIN
212
+ -- Update engagement scores based on recent activity
213
+ UPDATE user_engagement_patterns
214
+ SET
215
+ engagement_score = COALESCE((
216
+ SELECT
217
+ CASE
218
+ WHEN AVG(user_satisfaction) >= 4.5 THEN 0.9
219
+ WHEN AVG(user_satisfaction) >= 4.0 THEN 0.8
220
+ WHEN AVG(user_satisfaction) >= 3.5 THEN 0.7
221
+ WHEN AVG(user_satisfaction) >= 3.0 THEN 0.6
222
+ ELSE 0.5
223
+ END
224
+ FROM brand_interactions bi
225
+ WHERE bi.user_id = user_engagement_patterns.user_id
226
+ AND bi.created_at >= NOW() - INTERVAL '30 days'
227
+ ), 0.5),
228
+ total_interactions = COALESCE((
229
+ SELECT COUNT(*)
230
+ FROM brand_interactions bi
231
+ WHERE bi.user_id = user_engagement_patterns.user_id
232
+ ), 0),
233
+ last_interaction = COALESCE((
234
+ SELECT MAX(created_at)
235
+ FROM brand_interactions bi
236
+ WHERE bi.user_id = user_engagement_patterns.user_id
237
+ ), last_interaction),
238
+ updated_at = NOW();
239
+ END;
240
+ $$ LANGUAGE plpgsql;
241
+
242
+ -- Triggers for automatic updates
243
+ CREATE OR REPLACE FUNCTION update_user_engagement_on_interaction()
244
+ RETURNS TRIGGER AS $$
245
+ BEGIN
246
+ INSERT INTO user_engagement_patterns (user_id, brand_name, total_interactions, last_interaction)
247
+ VALUES (NEW.user_id, NEW.brand_name, 1, NEW.created_at)
248
+ ON CONFLICT (user_id)
249
+ DO UPDATE SET
250
+ total_interactions = user_engagement_patterns.total_interactions + 1,
251
+ last_interaction = NEW.created_at,
252
+ updated_at = NOW();
253
+
254
+ RETURN NEW;
255
+ END;
256
+ $$ LANGUAGE plpgsql;
257
+
258
+ DROP TRIGGER IF EXISTS update_engagement_on_brand_interaction ON brand_interactions;
259
+ CREATE TRIGGER update_engagement_on_brand_interaction
260
+ AFTER INSERT ON brand_interactions
261
+ FOR EACH ROW
262
+ EXECUTE FUNCTION update_user_engagement_on_interaction();
263
+
264
+ -- Initialize circuit breaker states for known models
265
+ INSERT INTO circuit_breaker_states (model_name, state, threshold_failures, timeout_duration)
266
+ VALUES
267
+ ('gemini20FlashExp', 'closed', 5, '5 minutes'),
268
+ ('gemini15Flash', 'closed', 5, '5 minutes'),
269
+ ('xai-grok-3-mini-fast', 'closed', 3, '10 minutes')
270
+ ON CONFLICT (model_name) DO NOTHING;
271
+
272
+ -- Create materialized views for expensive queries (refresh periodically)
273
+ CREATE MATERIALIZED VIEW IF NOT EXISTS daily_brand_performance AS
274
+ SELECT
275
+ brand_name,
276
+ DATE(created_at) as date,
277
+ COUNT(DISTINCT user_id) as unique_users,
278
+ COUNT(*) as total_interactions,
279
+ AVG(user_satisfaction) as avg_satisfaction,
280
+ COUNT(CASE WHEN interaction_type = 'product_inquiry' THEN 1 END) as product_inquiries,
281
+ COUNT(CASE WHEN interaction_type = 'store_locator' THEN 1 END) as store_requests,
282
+ COUNT(CASE WHEN user_satisfaction >= 4 THEN 1 END) as satisfied_users
283
+ FROM brand_interactions
284
+ WHERE created_at >= CURRENT_DATE - INTERVAL '90 days'
285
+ GROUP BY brand_name, DATE(created_at)
286
+ ORDER BY date DESC, brand_name;
287
+
288
+ -- Index on materialized view
289
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_daily_brand_performance_unique ON daily_brand_performance (brand_name, date);
290
+
291
+ -- Comments for documentation
292
+ COMMENT ON TABLE brand_interactions IS 'Tracks user interactions specific to each brand (Sienna Naturals, Myavana)';
293
+ COMMENT ON TABLE store_locator_requests IS 'Tracks store locator requests for physical location recommendations';
294
+ COMMENT ON TABLE response_cache IS 'Intelligent response caching with brand and user context';
295
+ COMMENT ON TABLE circuit_breaker_states IS 'Circuit breaker states for AI model reliability';
296
+ COMMENT ON TABLE smart_prompt_analytics IS 'Analytics for smart prompt generation effectiveness';
297
+ COMMENT ON TABLE user_engagement_patterns IS 'User engagement patterns and preferences by brand';
298
+
299
+ COMMENT ON FUNCTION cleanup_expired_cache() IS 'Removes expired cache entries, should be run periodically';
300
+ COMMENT ON FUNCTION update_engagement_patterns() IS 'Updates user engagement scores based on recent activity';
301
+
302
+ -- Grant permissions (adjust as needed for your setup)
303
+ -- GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO your_app_user;
304
+ -- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO your_app_user;
305
+ -- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO your_app_user;
@@ -0,0 +1,437 @@
1
+ -- Complete Migration Runner Script
2
+ -- Run this to apply all migrations in the correct order
3
+ -- This script is safe to run multiple times (idempotent)
4
+
5
+ -- Migration tracking table
6
+ CREATE TABLE IF NOT EXISTS schema_migrations (
7
+ id SERIAL PRIMARY KEY,
8
+ migration_name VARCHAR(255) NOT NULL UNIQUE,
9
+ applied_at TIMESTAMP DEFAULT NOW(),
10
+ checksum VARCHAR(64)
11
+ );
12
+
13
+ -- Check if migration has been applied
14
+ CREATE OR REPLACE FUNCTION migration_applied(migration_name TEXT)
15
+ RETURNS BOOLEAN AS $$
16
+ BEGIN
17
+ RETURN EXISTS(SELECT 1 FROM schema_migrations WHERE schema_migrations.migration_name = migration_applied.migration_name);
18
+ END;
19
+ $$ LANGUAGE plpgsql;
20
+
21
+ -- Apply migration if not already applied
22
+ CREATE OR REPLACE FUNCTION apply_migration(migration_name TEXT, migration_checksum TEXT DEFAULT NULL)
23
+ RETURNS VOID AS $$
24
+ BEGIN
25
+ IF NOT migration_applied(migration_name) THEN
26
+ INSERT INTO schema_migrations (migration_name, checksum)
27
+ VALUES (migration_name, migration_checksum);
28
+ RAISE NOTICE 'Applied migration: %', migration_name;
29
+ ELSE
30
+ RAISE NOTICE 'Migration already applied: %', migration_name;
31
+ END IF;
32
+ END;
33
+ $$ LANGUAGE plpgsql;
34
+
35
+ -- Start migration process
36
+ DO $$
37
+ BEGIN
38
+ RAISE NOTICE 'Starting database migrations for Sienna Naturals Enhanced System...';
39
+ RAISE NOTICE 'Timestamp: %', NOW();
40
+ END $$;
41
+
42
+ -- Core tables (should already exist, but ensure they're there)
43
+ CREATE TABLE IF NOT EXISTS users (
44
+ user_id VARCHAR(255) PRIMARY KEY,
45
+ name VARCHAR(255),
46
+ hair_type VARCHAR(100),
47
+ hair_concerns TEXT[],
48
+ location VARCHAR(255),
49
+ brand_preference VARCHAR(100) DEFAULT 'myavana',
50
+ created_at TIMESTAMP DEFAULT NOW(),
51
+ updated_at TIMESTAMP DEFAULT NOW()
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS conversations (
55
+ id SERIAL PRIMARY KEY,
56
+ conversation_id VARCHAR(255) UNIQUE NOT NULL,
57
+ user_id VARCHAR(255) REFERENCES users(user_id),
58
+ chat_history JSONB,
59
+ summary TEXT,
60
+ brand VARCHAR(100) DEFAULT 'myavana',
61
+ created_at TIMESTAMP DEFAULT NOW(),
62
+ updated_at TIMESTAMP DEFAULT NOW()
63
+ );
64
+
65
+ CREATE TABLE IF NOT EXISTS sessions (
66
+ session_id VARCHAR(255) PRIMARY KEY,
67
+ user_id VARCHAR(255),
68
+ session_data JSONB,
69
+ brand VARCHAR(100),
70
+ last_accessed_at TIMESTAMP DEFAULT NOW(),
71
+ expires_at TIMESTAMP DEFAULT NOW() + INTERVAL '7 days'
72
+ );
73
+
74
+ -- Core indexes
75
+ CREATE INDEX IF NOT EXISTS idx_users_brand_created ON users(brand_preference, created_at);
76
+ CREATE INDEX IF NOT EXISTS idx_conversations_user_brand ON conversations(user_id, brand, created_at);
77
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_expires ON sessions(user_id, expires_at);
78
+
79
+ SELECT apply_migration('000_core_tables');
80
+
81
+ -- Migration 001: Analytics Tables
82
+ DO $$
83
+ BEGIN
84
+ IF NOT migration_applied('001_add_analytics_tables') THEN
85
+ RAISE NOTICE 'Applying Migration 001: Analytics Tables...';
86
+
87
+ -- All the analytics tables from 001_add_analytics_tables.sql
88
+ CREATE TABLE IF NOT EXISTS prompt_analytics (
89
+ id SERIAL PRIMARY KEY,
90
+ user_id VARCHAR(255) NOT NULL,
91
+ query_type VARCHAR(100),
92
+ topics_detected JSONB,
93
+ prompt_length INTEGER,
94
+ faqs_used INTEGER,
95
+ products_used INTEGER,
96
+ created_at TIMESTAMP DEFAULT NOW()
97
+ );
98
+
99
+ CREATE TABLE IF NOT EXISTS conversation_metrics (
100
+ id SERIAL PRIMARY KEY,
101
+ user_id VARCHAR(255) NOT NULL,
102
+ conversation_id VARCHAR(255) NOT NULL,
103
+ message_count INTEGER DEFAULT 0,
104
+ ai_model_used VARCHAR(100),
105
+ response_time_ms INTEGER,
106
+ user_satisfaction INTEGER,
107
+ conversion_event VARCHAR(100),
108
+ created_at TIMESTAMP DEFAULT NOW()
109
+ );
110
+
111
+ CREATE TABLE IF NOT EXISTS user_journey_events (
112
+ id SERIAL PRIMARY KEY,
113
+ user_id VARCHAR(255) NOT NULL,
114
+ event_type VARCHAR(100) NOT NULL,
115
+ event_data JSONB,
116
+ hair_journey_stage VARCHAR(100),
117
+ created_at TIMESTAMP DEFAULT NOW()
118
+ );
119
+
120
+ CREATE TABLE IF NOT EXISTS ai_model_performance (
121
+ id SERIAL PRIMARY KEY,
122
+ model_name VARCHAR(100) NOT NULL,
123
+ request_count INTEGER DEFAULT 1,
124
+ success_count INTEGER DEFAULT 0,
125
+ failure_count INTEGER DEFAULT 0,
126
+ avg_response_time_ms FLOAT,
127
+ date_tracked DATE DEFAULT CURRENT_DATE,
128
+ UNIQUE(model_name, date_tracked)
129
+ );
130
+
131
+ CREATE TABLE IF NOT EXISTS cache_analytics (
132
+ id SERIAL PRIMARY KEY,
133
+ cache_type VARCHAR(100) NOT NULL,
134
+ hit_count INTEGER DEFAULT 0,
135
+ miss_count INTEGER DEFAULT 0,
136
+ cache_key_pattern VARCHAR(255),
137
+ date_tracked DATE DEFAULT CURRENT_DATE,
138
+ UNIQUE(cache_type, cache_key_pattern, date_tracked)
139
+ );
140
+
141
+ -- Indexes
142
+ CREATE INDEX IF NOT EXISTS idx_prompt_analytics_user_created ON prompt_analytics(user_id, created_at);
143
+ CREATE INDEX IF NOT EXISTS idx_conversation_metrics_user ON conversation_metrics(user_id, created_at);
144
+ CREATE INDEX IF NOT EXISTS idx_user_journey_events_user ON user_journey_events(user_id, created_at);
145
+ CREATE INDEX IF NOT EXISTS idx_ai_model_performance_date ON ai_model_performance(date_tracked);
146
+
147
+ -- Views
148
+ CREATE OR REPLACE VIEW daily_conversation_stats AS
149
+ SELECT
150
+ DATE(created_at) as date,
151
+ COUNT(DISTINCT user_id) as unique_users,
152
+ COUNT(*) as total_conversations,
153
+ AVG(message_count) as avg_messages_per_conversation,
154
+ COUNT(CASE WHEN conversion_event IS NOT NULL THEN 1 END) as conversions
155
+ FROM conversation_metrics
156
+ GROUP BY DATE(created_at)
157
+ ORDER BY date DESC;
158
+
159
+ CREATE OR REPLACE VIEW model_reliability_stats AS
160
+ SELECT
161
+ model_name,
162
+ SUM(request_count) as total_requests,
163
+ SUM(success_count) as total_successes,
164
+ SUM(failure_count) as total_failures,
165
+ ROUND((SUM(success_count)::float / SUM(request_count)::float * 100), 2) as success_rate,
166
+ ROUND(AVG(avg_response_time_ms), 2) as avg_response_time
167
+ FROM ai_model_performance
168
+ WHERE date_tracked >= CURRENT_DATE - INTERVAL '30 days'
169
+ GROUP BY model_name;
170
+
171
+ SELECT apply_migration('001_add_analytics_tables');
172
+ END IF;
173
+ END $$;
174
+
175
+ -- Migration 002: Advanced Features
176
+ DO $$
177
+ BEGIN
178
+ IF NOT migration_applied('002_add_advanced_features_tables') THEN
179
+ RAISE NOTICE 'Applying Migration 002: Advanced Features Tables...';
180
+
181
+ -- All tables from 002_add_advanced_features_tables.sql
182
+ CREATE TABLE IF NOT EXISTS error_logs (
183
+ id SERIAL PRIMARY KEY,
184
+ user_id VARCHAR(255) NOT NULL,
185
+ conversation_id VARCHAR(255),
186
+ error_type VARCHAR(100) NOT NULL,
187
+ error_message TEXT,
188
+ model_name VARCHAR(100),
189
+ context_data JSONB,
190
+ recovery_attempted BOOLEAN DEFAULT FALSE,
191
+ recovery_successful BOOLEAN DEFAULT FALSE,
192
+ created_at TIMESTAMP DEFAULT NOW()
193
+ );
194
+
195
+ CREATE TABLE IF NOT EXISTS personalization_metrics (
196
+ id SERIAL PRIMARY KEY,
197
+ user_id VARCHAR(255) NOT NULL,
198
+ persona_assigned VARCHAR(100),
199
+ query_length INTEGER,
200
+ response_length INTEGER,
201
+ personalization_applied BOOLEAN DEFAULT FALSE,
202
+ user_satisfaction_score INTEGER,
203
+ created_at TIMESTAMP DEFAULT NOW()
204
+ );
205
+
206
+ CREATE TABLE IF NOT EXISTS user_preferences (
207
+ id SERIAL PRIMARY KEY,
208
+ user_id VARCHAR(255) NOT NULL UNIQUE,
209
+ persona_type VARCHAR(100),
210
+ communication_style VARCHAR(100),
211
+ preferred_response_format VARCHAR(100),
212
+ hair_priorities JSONB,
213
+ product_preferences JSONB,
214
+ engagement_preferences JSONB,
215
+ created_at TIMESTAMP DEFAULT NOW(),
216
+ updated_at TIMESTAMP DEFAULT NOW()
217
+ );
218
+
219
+ CREATE TABLE IF NOT EXISTS proactive_campaigns (
220
+ id SERIAL PRIMARY KEY,
221
+ campaign_name VARCHAR(255) NOT NULL,
222
+ campaign_type VARCHAR(100) NOT NULL,
223
+ target_persona VARCHAR(100),
224
+ trigger_conditions JSONB,
225
+ message_template JSONB,
226
+ active BOOLEAN DEFAULT TRUE,
227
+ created_at TIMESTAMP DEFAULT NOW()
228
+ );
229
+
230
+ CREATE TABLE IF NOT EXISTS proactive_engagement_schedule (
231
+ id SERIAL PRIMARY KEY,
232
+ user_id VARCHAR(255) NOT NULL,
233
+ trigger_type VARCHAR(100) NOT NULL,
234
+ scheduled_time TIMESTAMP NOT NULL,
235
+ context_data JSONB,
236
+ sent BOOLEAN DEFAULT FALSE,
237
+ sent_at TIMESTAMP,
238
+ active BOOLEAN DEFAULT TRUE,
239
+ created_at TIMESTAMP DEFAULT NOW(),
240
+ UNIQUE(user_id, trigger_type)
241
+ );
242
+
243
+ CREATE TABLE IF NOT EXISTS proactive_engagement_history (
244
+ id SERIAL PRIMARY KEY,
245
+ user_id VARCHAR(255) NOT NULL,
246
+ campaign_id INTEGER REFERENCES proactive_campaigns(id),
247
+ message_sent JSONB,
248
+ delivery_status VARCHAR(50) DEFAULT 'pending',
249
+ user_response TEXT,
250
+ engagement_score FLOAT,
251
+ created_at TIMESTAMP DEFAULT NOW(),
252
+ responded_at TIMESTAMP
253
+ );
254
+
255
+ CREATE TABLE IF NOT EXISTS hair_issues (
256
+ id SERIAL PRIMARY KEY,
257
+ user_id VARCHAR(255) NOT NULL,
258
+ issue_description TEXT NOT NULL,
259
+ severity_level INTEGER,
260
+ issue_category VARCHAR(100),
261
+ reported_context TEXT,
262
+ resolution_status VARCHAR(50) DEFAULT 'open',
263
+ resolution_notes TEXT,
264
+ created_at TIMESTAMP DEFAULT NOW(),
265
+ resolved_at TIMESTAMP
266
+ );
267
+
268
+ -- Indexes
269
+ CREATE INDEX IF NOT EXISTS idx_error_logs_user_created ON error_logs(user_id, created_at);
270
+ CREATE INDEX IF NOT EXISTS idx_error_logs_type_model ON error_logs(error_type, model_name);
271
+ CREATE INDEX IF NOT EXISTS idx_personalization_metrics_user ON personalization_metrics(user_id, created_at);
272
+ CREATE INDEX IF NOT EXISTS idx_user_preferences_user ON user_preferences(user_id);
273
+ CREATE INDEX IF NOT EXISTS idx_proactive_engagement_user ON proactive_engagement_history(user_id, created_at);
274
+ CREATE INDEX IF NOT EXISTS idx_hair_issues_user_status ON hair_issues(user_id, resolution_status);
275
+
276
+ -- Update trigger for user_preferences
277
+ CREATE OR REPLACE FUNCTION update_user_preferences_updated_at()
278
+ RETURNS TRIGGER AS $func$
279
+ BEGIN
280
+ NEW.updated_at = NOW();
281
+ RETURN NEW;
282
+ END;
283
+ $func$ LANGUAGE plpgsql;
284
+
285
+ DROP TRIGGER IF EXISTS update_user_preferences_updated_at_trigger ON user_preferences;
286
+ CREATE TRIGGER update_user_preferences_updated_at_trigger
287
+ BEFORE UPDATE ON user_preferences
288
+ FOR EACH ROW
289
+ EXECUTE FUNCTION update_user_preferences_updated_at();
290
+
291
+ SELECT apply_migration('002_add_advanced_features_tables');
292
+ END IF;
293
+ END $$;
294
+
295
+ -- Migration 003: Sienna Naturals Enhancements
296
+ DO $$
297
+ BEGIN
298
+ IF NOT migration_applied('003_sienna_naturals_enhancements') THEN
299
+ RAISE NOTICE 'Applying Migration 003: Sienna Naturals Enhancements...';
300
+
301
+ -- Brand-specific tables
302
+ CREATE TABLE IF NOT EXISTS brand_interactions (
303
+ id SERIAL PRIMARY KEY,
304
+ user_id VARCHAR(255) NOT NULL,
305
+ conversation_id VARCHAR(255),
306
+ brand_name VARCHAR(100) NOT NULL,
307
+ interaction_type VARCHAR(100),
308
+ interaction_data JSONB,
309
+ user_satisfaction INTEGER,
310
+ created_at TIMESTAMP DEFAULT NOW()
311
+ );
312
+
313
+ CREATE TABLE IF NOT EXISTS store_locator_requests (
314
+ id SERIAL PRIMARY KEY,
315
+ user_id VARCHAR(255) NOT NULL,
316
+ location_query VARCHAR(255),
317
+ user_location JSONB,
318
+ stores_found INTEGER DEFAULT 0,
319
+ stores_data JSONB,
320
+ created_at TIMESTAMP DEFAULT NOW()
321
+ );
322
+
323
+ CREATE TABLE IF NOT EXISTS circuit_breaker_states (
324
+ id SERIAL PRIMARY KEY,
325
+ model_name VARCHAR(100) NOT NULL UNIQUE,
326
+ state VARCHAR(20) DEFAULT 'closed',
327
+ failure_count INTEGER DEFAULT 0,
328
+ last_failure_time TIMESTAMP,
329
+ last_success_time TIMESTAMP,
330
+ threshold_failures INTEGER DEFAULT 5,
331
+ timeout_duration INTERVAL DEFAULT '5 minutes',
332
+ updated_at TIMESTAMP DEFAULT NOW()
333
+ );
334
+
335
+ CREATE TABLE IF NOT EXISTS response_cache (
336
+ id SERIAL PRIMARY KEY,
337
+ cache_key VARCHAR(512) NOT NULL UNIQUE,
338
+ brand_name VARCHAR(100),
339
+ cached_response JSONB NOT NULL,
340
+ user_context_hash VARCHAR(64),
341
+ query_hash VARCHAR(64),
342
+ context_analysis JSONB,
343
+ hit_count INTEGER DEFAULT 0,
344
+ created_at TIMESTAMP DEFAULT NOW(),
345
+ expires_at TIMESTAMP DEFAULT NOW() + INTERVAL '24 hours'
346
+ );
347
+
348
+ CREATE TABLE IF NOT EXISTS user_engagement_patterns (
349
+ id SERIAL PRIMARY KEY,
350
+ user_id VARCHAR(255) NOT NULL UNIQUE,
351
+ brand_name VARCHAR(100),
352
+ total_interactions INTEGER DEFAULT 0,
353
+ avg_session_duration INTERVAL,
354
+ preferred_interaction_time TIME,
355
+ preferred_day_of_week INTEGER,
356
+ response_style_preference VARCHAR(100),
357
+ topic_preferences JSONB,
358
+ engagement_score FLOAT DEFAULT 0,
359
+ last_interaction TIMESTAMP,
360
+ updated_at TIMESTAMP DEFAULT NOW()
361
+ );
362
+
363
+ -- Indexes
364
+ CREATE INDEX IF NOT EXISTS idx_brand_interactions_user_brand ON brand_interactions(user_id, brand_name, created_at);
365
+ CREATE INDEX IF NOT EXISTS idx_response_cache_key ON response_cache(cache_key, expires_at) WHERE expires_at > NOW();
366
+ CREATE INDEX IF NOT EXISTS idx_circuit_breaker_model ON circuit_breaker_states(model_name, state);
367
+ CREATE INDEX IF NOT EXISTS idx_user_engagement_brand ON user_engagement_patterns(brand_name, engagement_score DESC);
368
+
369
+ -- Initialize circuit breaker states
370
+ INSERT INTO circuit_breaker_states (model_name, state, threshold_failures, timeout_duration)
371
+ VALUES
372
+ ('gemini20FlashExp', 'closed', 5, '5 minutes'),
373
+ ('gemini15Flash', 'closed', 5, '5 minutes'),
374
+ ('xai-grok-3-mini-fast', 'closed', 3, '10 minutes')
375
+ ON CONFLICT (model_name) DO NOTHING;
376
+
377
+ -- Trigger for engagement patterns
378
+ CREATE OR REPLACE FUNCTION update_user_engagement_on_interaction()
379
+ RETURNS TRIGGER AS $func$
380
+ BEGIN
381
+ INSERT INTO user_engagement_patterns (user_id, brand_name, total_interactions, last_interaction)
382
+ VALUES (NEW.user_id, NEW.brand_name, 1, NEW.created_at)
383
+ ON CONFLICT (user_id)
384
+ DO UPDATE SET
385
+ total_interactions = user_engagement_patterns.total_interactions + 1,
386
+ last_interaction = NEW.created_at,
387
+ updated_at = NOW();
388
+
389
+ RETURN NEW;
390
+ END;
391
+ $func$ LANGUAGE plpgsql;
392
+
393
+ DROP TRIGGER IF EXISTS update_engagement_on_brand_interaction ON brand_interactions;
394
+ CREATE TRIGGER update_engagement_on_brand_interaction
395
+ AFTER INSERT ON brand_interactions
396
+ FOR EACH ROW
397
+ EXECUTE FUNCTION update_user_engagement_on_interaction();
398
+
399
+ SELECT apply_migration('003_sienna_naturals_enhancements');
400
+ END IF;
401
+ END $$;
402
+
403
+ -- Final cleanup and optimization
404
+ DO $$
405
+ BEGIN
406
+ RAISE NOTICE 'Running post-migration optimizations...';
407
+
408
+ -- Update statistics
409
+ ANALYZE;
410
+
411
+ -- Clean up any expired cache entries
412
+ DELETE FROM response_cache WHERE expires_at < NOW();
413
+
414
+ RAISE NOTICE 'All migrations completed successfully!';
415
+ RAISE NOTICE 'Database is now ready for Sienna Naturals Enhanced System';
416
+
417
+ -- Show migration status
418
+ RAISE NOTICE 'Applied migrations:';
419
+ FOR rec IN SELECT migration_name, applied_at FROM schema_migrations ORDER BY applied_at LOOP
420
+ RAISE NOTICE ' - % (applied: %)', rec.migration_name, rec.applied_at;
421
+ END LOOP;
422
+ END $$;
423
+
424
+ -- Verification queries
425
+ SELECT 'Migration Status' as info;
426
+ SELECT migration_name, applied_at FROM schema_migrations ORDER BY applied_at;
427
+
428
+ SELECT 'Table Counts' as info;
429
+ SELECT
430
+ schemaname as schema,
431
+ tablename as table_name,
432
+ n_tup_ins as inserts,
433
+ n_tup_upd as updates,
434
+ n_tup_del as deletes
435
+ FROM pg_stat_user_tables
436
+ WHERE schemaname = 'public'
437
+ ORDER BY tablename;
package/package.json CHANGED
@@ -1,37 +1,37 @@
1
1
  {
2
2
  "name": "myavana-bot-test-core",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Shared bot functionality with enhanced features",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "jest",
8
- "test:watch": "jest --watch",
9
- "test:coverage": "jest --coverage",
10
- "lint": "eslint src/",
11
- "lint:fix": "eslint src/ --fix"
7
+ "test": "jest",
8
+ "test:watch": "jest --watch",
9
+ "test:coverage": "jest --coverage",
10
+ "lint": "eslint src/",
11
+ "lint:fix": "eslint src/ --fix"
12
12
  },
13
13
  "dependencies": {
14
- "@genkit-ai/googleai": "^0.9.12",
15
- "dotenv": "^16.4.7",
16
- "genkit": "^0.9.12",
17
- "pg": "^8.13.1",
18
- "redis": "^4.7.0",
19
- "winston": "^3.11.0",
20
- "joi": "^17.11.0",
21
- "isomorphic-dompurify": "^2.9.0",
22
- "express-rate-limit": "^7.1.5",
23
- "express-slow-down": "^2.0.1",
24
- "node-cache": "^5.1.2",
25
- "uuid": "^9.0.1"
14
+ "@genkit-ai/googleai": "^0.9.12",
15
+ "dotenv": "^16.4.7",
16
+ "express-rate-limit": "^7.1.5",
17
+ "express-slow-down": "^2.0.1",
18
+ "genkit": "^0.9.12",
19
+ "isomorphic-dompurify": "^2.9.0",
20
+ "joi": "^17.11.0",
21
+ "node-cache": "^5.1.2",
22
+ "pg": "^8.13.1",
23
+ "redis": "^4.7.0",
24
+ "uuid": "^9.0.1",
25
+ "winston": "^3.11.0"
26
26
  },
27
27
  "devDependencies": {
28
- "jest": "^29.7.0",
29
- "babel-jest": "^29.7.0",
30
- "@babel/preset-env": "^7.23.6",
31
- "eslint": "^8.56.0",
32
- "supertest": "^6.3.3"
28
+ "@babel/preset-env": "^7.23.6",
29
+ "babel-jest": "^29.7.0",
30
+ "eslint": "^8.56.0",
31
+ "jest": "^29.7.0",
32
+ "supertest": "^6.3.3"
33
33
  },
34
34
  "engines": {
35
- "node": ">=18.0.0"
35
+ "node": ">=18.0.0"
36
36
  }
37
- }
37
+ }
package/src/ai.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // ai.js
2
2
  const { genkit } = require('genkit');
3
- const { googleAI, gemini15Flash, gemini20FlashExp } = require('@genkit-ai/googleai');
3
+ const { googleAI, gemini15Flash, gemini20FlashExp, gemini } = require('@genkit-ai/googleai');
4
4
  const config = require('./config');
5
5
 
6
6
  const ai = genkit({
7
7
  plugins: [googleAI({
8
8
  apiKey: config.genkitApiKey,
9
9
  })],
10
- model: gemini20FlashExp,
10
+ model: gemini('gemini-3-flash-preview'),
11
11
  });
12
12
 
13
13
  module.exports = ai;
@@ -1,7 +1,7 @@
1
1
  // conversation.js
2
2
  const { pgClient, redisClient } = require('./database');
3
3
  const ai = require('./ai');
4
- const {gemini15Flash} = require("@genkit-ai/googleai");
4
+ const {gemini15Flash, gemini} = require("@genkit-ai/googleai");
5
5
 
6
6
  const saveConversationSummary = async (conversationId, userId, summary) => {
7
7
  const query = `
@@ -28,7 +28,7 @@ const generateConversationSummary = async (chatHistory) => {
28
28
  }
29
29
  const summaryPrompt = `Summarize the following conversation, getting all important information, especially the user's hair details, name, and location: ${JSON.stringify(chatHistory)}`;
30
30
  const { text } = await ai.chat({
31
- model: gemini15Flash,
31
+ model: gemini('gemini-2.5-flash'),
32
32
  system: summaryPrompt,
33
33
  });
34
34
  return text;
@@ -1,6 +1,6 @@
1
1
  // postResponseProcessing.js
2
2
  const ai = require("./ai");
3
- const {gemini15Flash} = require("@genkit-ai/googleai");
3
+ const {gemini15Flash, gemini} = require("@genkit-ai/googleai");
4
4
  const DatabaseSessionStore = require("./session");
5
5
  const { pgClient, redisClient } = require('./database');
6
6
  const {generateAndSaveSummary, saveConversationSummary, saveConversation} = require("./conversation");
@@ -226,7 +226,7 @@ const postProcessConversation = async (chatHistory, message) => {
226
226
  //console.log('Extraction prompt: ', extractionPrompt);
227
227
  try {
228
228
  const chat = ai.chat({
229
- model: gemini15Flash,
229
+ model: gemini('gemini-2.5-flash'),
230
230
  config: {
231
231
  temperature: 1.1,
232
232
  },
@@ -385,7 +385,7 @@ const postResponseProductCheck = async (chatHistory, message) => {
385
385
  // Try Gemini first
386
386
  try {
387
387
  const chat = ai.chat({
388
- model: gemini15Flash, // or gemini20FlashExp
388
+ model: gemini('gemini-2.5-flash'), // or gemini20FlashExp
389
389
  config: {
390
390
  temperature: 0.3, // Reduced temperature for more consistent JSON formatting
391
391
  },
@@ -1,6 +1,6 @@
1
1
  // unifiedChatHandler.js - Fixed version with proper model prioritization
2
2
  const { genkit, z } = require('genkit');
3
- const { gemini15Flash, gemini15Pro, gemini20FlashExp } = require('@genkit-ai/googleai');
3
+ const { gemini15Flash, gemini15Pro, gemini20FlashExp, gemini } = require('@genkit-ai/googleai');
4
4
 
5
5
  class UnifiedChatHandler {
6
6
  constructor(config) {
@@ -17,9 +17,9 @@ class UnifiedChatHandler {
17
17
 
18
18
  // Initialize model priority list - GEMINI FIRST!
19
19
  this.modelPriority = [
20
- { name: 'gemini20FlashExp', model: gemini20FlashExp, type: 'primary' },
21
- { name: 'gemini15Flash', model: gemini15Flash, type: 'primary' },
22
- { name: 'gemini15Pro', model: gemini15Pro, type: 'backup' }
20
+ { name: 'gemini20FlashExp', model: gemini('gemini-3-flash-preview'), type: 'primary' },
21
+ { name: 'gemini15Flash', model: gemini('gemini-2.5-flash'), type: 'primary' },
22
+ { name: 'gemini15Pro', model: gemini('gemini-3-pro-preview'), type: 'backup' }
23
23
  ];
24
24
 
25
25
  // Only initialize xAI as last resort if API key exists