1#[allow(unused_imports)]
5use gestura_core_foundation::error::AppError;
6use std::collections::HashMap;
7use std::sync::Arc;
8use tokio::sync::RwLock;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12pub enum RecommendationType {
13 Feature,
14 Gesture,
15 VoiceCommand,
16 Setting,
17 Workflow,
18 Tutorial,
19 Optimization,
20}
21
22#[derive(Debug, Clone, serde::Serialize)]
24pub struct Recommendation {
25 pub id: String,
26 pub title: String,
27 pub description: String,
28 pub recommendation_type: RecommendationType,
29 pub confidence: f32,
30 pub priority: u8, pub category: String,
32 pub tags: Vec<String>,
33 pub action_url: Option<String>,
34 pub estimated_benefit: String,
35 pub created_at: chrono::DateTime<chrono::Utc>,
36}
37
38#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
40pub struct UserBehaviorPattern {
41 pub user_id: String,
42 pub feature_usage: HashMap<String, u32>,
43 pub gesture_frequency: HashMap<String, u32>,
44 pub voice_command_frequency: HashMap<String, u32>,
45 pub session_patterns: SessionPatterns,
46 pub error_patterns: HashMap<String, u32>,
47 pub preference_scores: HashMap<String, f32>,
48 pub last_updated: chrono::DateTime<chrono::Utc>,
49}
50
51#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
53pub struct SessionPatterns {
54 pub average_session_duration_minutes: f32,
55 pub peak_usage_hours: Vec<u8>,
56 pub most_used_features: Vec<String>,
57 pub feature_adoption_rate: f32,
58 pub error_rate: f32,
59}
60
61impl Default for SessionPatterns {
62 fn default() -> Self {
63 Self {
64 average_session_duration_minutes: 0.0,
65 peak_usage_hours: Vec::new(),
66 most_used_features: Vec::new(),
67 feature_adoption_rate: 0.0,
68 error_rate: 0.0,
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct RecommendationConfig {
76 pub max_recommendations: usize,
77 pub min_confidence_threshold: f32,
78 pub learning_rate: f32,
79 pub recommendation_refresh_hours: u64,
80 pub enable_cross_user_learning: bool,
81 pub privacy_mode: bool,
82}
83
84impl Default for RecommendationConfig {
85 fn default() -> Self {
86 Self {
87 max_recommendations: 10,
88 min_confidence_threshold: 0.6,
89 learning_rate: 0.1,
90 recommendation_refresh_hours: 24,
91 enable_cross_user_learning: false,
92 privacy_mode: true,
93 }
94 }
95}
96
97pub struct PersonalizedRecommendationEngine {
99 user_patterns: Arc<RwLock<HashMap<String, UserBehaviorPattern>>>,
100 recommendations_cache: Arc<RwLock<HashMap<String, Vec<Recommendation>>>>,
101 global_patterns: Arc<RwLock<HashMap<String, f32>>>,
102 config: Arc<RwLock<RecommendationConfig>>,
103 recommendation_templates: Arc<RwLock<Vec<RecommendationTemplate>>>,
104}
105
106#[derive(Debug, Clone)]
108struct RecommendationTemplate {
109 #[allow(dead_code)]
110 id: String,
111 title_template: String,
112 description_template: String,
113 recommendation_type: RecommendationType,
114 category: String,
115 tags: Vec<String>,
116 conditions: Vec<RecommendationCondition>,
117 priority: u8,
118 estimated_benefit: String,
119}
120
121#[derive(Debug, Clone)]
123#[allow(dead_code)]
124enum RecommendationCondition {
125 FeatureUsageBelow { feature: String, threshold: u32 },
126 FeatureUsageAbove { feature: String, threshold: u32 },
127 ErrorRateAbove { threshold: f32 },
128 SessionDurationBelow { threshold_minutes: f32 },
129 GestureAccuracyBelow { threshold: f32 },
130 VoiceAccuracyBelow { threshold: f32 },
131 HasNotUsedFeature { feature: String },
132 UsesFeatureFrequently { feature: String, min_usage: u32 },
133}
134
135impl PersonalizedRecommendationEngine {
136 pub fn new(config: RecommendationConfig) -> Self {
138 let engine = Self {
139 user_patterns: Arc::new(RwLock::new(HashMap::new())),
140 recommendations_cache: Arc::new(RwLock::new(HashMap::new())),
141 global_patterns: Arc::new(RwLock::new(HashMap::new())),
142 config: Arc::new(RwLock::new(config)),
143 recommendation_templates: Arc::new(RwLock::new(Vec::new())),
144 };
145
146 tokio::spawn({
148 let engine = engine.clone();
149 async move {
150 if let Err(e) = engine.initialize_default_templates().await {
151 tracing::error!("Failed to initialize recommendation templates: {}", e);
152 }
153 }
154 });
155
156 engine
157 }
158
159 pub async fn update_user_pattern(
161 &self,
162 user_id: &str,
163 usage_data: serde_json::Value,
164 ) -> Result<(), AppError> {
165 let mut patterns = self.user_patterns.write().await;
166
167 let pattern = patterns
168 .entry(user_id.to_string())
169 .or_insert_with(|| UserBehaviorPattern {
170 user_id: user_id.to_string(),
171 feature_usage: HashMap::new(),
172 gesture_frequency: HashMap::new(),
173 voice_command_frequency: HashMap::new(),
174 session_patterns: SessionPatterns::default(),
175 error_patterns: HashMap::new(),
176 preference_scores: HashMap::new(),
177 last_updated: chrono::Utc::now(),
178 });
179
180 if let Some(features) = usage_data.get("features")
182 && let Ok(feature_map) =
183 serde_json::from_value::<HashMap<String, u32>>(features.clone())
184 {
185 for (feature, count) in feature_map {
186 *pattern.feature_usage.entry(feature).or_insert(0) += count;
187 }
188 }
189
190 if let Some(gestures) = usage_data.get("gestures")
191 && let Ok(gesture_map) =
192 serde_json::from_value::<HashMap<String, u32>>(gestures.clone())
193 {
194 for (gesture, count) in gesture_map {
195 *pattern.gesture_frequency.entry(gesture).or_insert(0) += count;
196 }
197 }
198
199 if let Some(voice_commands) = usage_data.get("voice_commands")
200 && let Ok(voice_map) =
201 serde_json::from_value::<HashMap<String, u32>>(voice_commands.clone())
202 {
203 for (command, count) in voice_map {
204 *pattern.voice_command_frequency.entry(command).or_insert(0) += count;
205 }
206 }
207
208 pattern.last_updated = chrono::Utc::now();
209
210 let mut cache = self.recommendations_cache.write().await;
212 cache.remove(user_id);
213
214 tracing::debug!("Updated behavior pattern for user: {}", user_id);
215 Ok(())
216 }
217
218 pub async fn generate_recommendations(
220 &self,
221 user_id: &str,
222 ) -> Result<Vec<Recommendation>, AppError> {
223 {
225 let cache = self.recommendations_cache.read().await;
226 if let Some(cached_recommendations) = cache.get(user_id) {
227 let config = self.config.read().await;
228 let cache_age = chrono::Utc::now().timestamp()
229 - cached_recommendations
230 .first()
231 .map(|r| r.created_at.timestamp())
232 .unwrap_or(0);
233
234 if cache_age < (config.recommendation_refresh_hours * 3600) as i64 {
235 return Ok(cached_recommendations.clone());
236 }
237 }
238 }
239
240 let patterns = self.user_patterns.read().await;
241 let user_pattern = patterns.get(user_id);
242
243 if user_pattern.is_none() {
244 return Ok(Vec::new()); }
246
247 let user_pattern = user_pattern.unwrap();
248 let templates = self.recommendation_templates.read().await;
249 let mut recommendations = Vec::new();
250
251 for template in templates.iter() {
253 if let Some(recommendation) = self.evaluate_template(template, user_pattern).await {
254 recommendations.push(recommendation);
255 }
256 }
257
258 recommendations.sort_by(|a, b| {
260 b.priority.cmp(&a.priority).then_with(|| {
261 b.confidence
262 .partial_cmp(&a.confidence)
263 .unwrap_or(std::cmp::Ordering::Equal)
264 })
265 });
266
267 let config = self.config.read().await;
269 recommendations.retain(|r| r.confidence >= config.min_confidence_threshold);
270 recommendations.truncate(config.max_recommendations);
271
272 drop(config);
274 drop(templates);
275 drop(patterns);
276 let mut cache = self.recommendations_cache.write().await;
277 cache.insert(user_id.to_string(), recommendations.clone());
278
279 tracing::info!(
280 "Generated {} recommendations for user: {}",
281 recommendations.len(),
282 user_id
283 );
284 Ok(recommendations)
285 }
286
287 async fn evaluate_template(
289 &self,
290 template: &RecommendationTemplate,
291 user_pattern: &UserBehaviorPattern,
292 ) -> Option<Recommendation> {
293 let mut confidence = 0.5; let mut conditions_met = 0;
295 let total_conditions = template.conditions.len();
296
297 for condition in &template.conditions {
299 match condition {
300 RecommendationCondition::FeatureUsageBelow { feature, threshold } => {
301 let usage = user_pattern.feature_usage.get(feature).unwrap_or(&0);
302 if *usage < *threshold {
303 conditions_met += 1;
304 confidence += 0.1;
305 }
306 }
307 RecommendationCondition::FeatureUsageAbove { feature, threshold } => {
308 let usage = user_pattern.feature_usage.get(feature).unwrap_or(&0);
309 if *usage > *threshold {
310 conditions_met += 1;
311 confidence += 0.1;
312 }
313 }
314 RecommendationCondition::ErrorRateAbove { threshold }
315 if user_pattern.session_patterns.error_rate > *threshold =>
316 {
317 conditions_met += 1;
318 confidence += 0.15;
319 }
320 RecommendationCondition::SessionDurationBelow { threshold_minutes }
321 if user_pattern
322 .session_patterns
323 .average_session_duration_minutes
324 < *threshold_minutes =>
325 {
326 conditions_met += 1;
327 confidence += 0.1;
328 }
329 RecommendationCondition::HasNotUsedFeature { feature }
330 if !user_pattern.feature_usage.contains_key(feature) =>
331 {
332 conditions_met += 1;
333 confidence += 0.2;
334 }
335 RecommendationCondition::UsesFeatureFrequently { feature, min_usage } => {
336 let usage = user_pattern.feature_usage.get(feature).unwrap_or(&0);
337 if *usage >= *min_usage {
338 conditions_met += 1;
339 confidence += 0.1;
340 }
341 }
342 _ => {} }
344 }
345
346 if total_conditions > 0 && (conditions_met as f32 / total_conditions as f32) < 0.5 {
348 return None;
349 }
350
351 if let Some(pref_score) = user_pattern.preference_scores.get(&template.category) {
353 confidence *= pref_score;
354 }
355
356 Some(Recommendation {
357 id: uuid::Uuid::new_v4().to_string(),
358 title: self.personalize_text(&template.title_template, user_pattern),
359 description: self.personalize_text(&template.description_template, user_pattern),
360 recommendation_type: template.recommendation_type.clone(),
361 confidence: confidence.min(1.0),
362 priority: template.priority,
363 category: template.category.clone(),
364 tags: template.tags.clone(),
365 action_url: None,
366 estimated_benefit: template.estimated_benefit.clone(),
367 created_at: chrono::Utc::now(),
368 })
369 }
370
371 fn personalize_text(&self, template: &str, user_pattern: &UserBehaviorPattern) -> String {
373 let mut result = template.to_string();
374
375 if let Some(most_used) = user_pattern.session_patterns.most_used_features.first() {
377 result = result.replace("{most_used_feature}", most_used);
378 }
379
380 result = result.replace(
381 "{session_duration}",
382 &format!(
383 "{:.1}",
384 user_pattern
385 .session_patterns
386 .average_session_duration_minutes
387 ),
388 );
389
390 result = result.replace(
391 "{error_rate}",
392 &format!("{:.1}%", user_pattern.session_patterns.error_rate * 100.0),
393 );
394
395 result
396 }
397
398 async fn initialize_default_templates(&self) -> Result<(), AppError> {
400 let mut templates = self.recommendation_templates.write().await;
401
402 templates.push(RecommendationTemplate {
403 id: "voice_training".to_string(),
404 title_template: "Improve Voice Recognition Accuracy".to_string(),
405 description_template: "Your voice recognition accuracy could be improved. Try the voice training feature to personalize the system to your voice.".to_string(),
406 recommendation_type: RecommendationType::Feature,
407 category: "voice".to_string(),
408 tags: vec!["accuracy".to_string(), "training".to_string()],
409 conditions: vec![
410 RecommendationCondition::HasNotUsedFeature { feature: "voice_training".to_string() },
411 RecommendationCondition::ErrorRateAbove { threshold: 0.1 },
412 ],
413 priority: 8,
414 estimated_benefit: "Up to 30% improvement in voice recognition accuracy".to_string(),
415 });
416
417 templates.push(RecommendationTemplate {
418 id: "gesture_customization".to_string(),
419 title_template: "Customize Your Gestures".to_string(),
420 description_template: "You use gestures frequently! Create custom gestures for your most common actions to save time.".to_string(),
421 recommendation_type: RecommendationType::Feature,
422 category: "gestures".to_string(),
423 tags: vec!["customization".to_string(), "efficiency".to_string()],
424 conditions: vec![
425 RecommendationCondition::UsesFeatureFrequently { feature: "gestures".to_string(), min_usage: 50 },
426 RecommendationCondition::HasNotUsedFeature { feature: "custom_gestures".to_string() },
427 ],
428 priority: 7,
429 estimated_benefit: "Reduce gesture time by up to 40%".to_string(),
430 });
431
432 templates.push(RecommendationTemplate {
433 id: "session_optimization".to_string(),
434 title_template: "Optimize Your Workflow".to_string(),
435 description_template: "Your sessions are shorter than average ({session_duration} min). Try these workflow optimizations to be more productive.".to_string(),
436 recommendation_type: RecommendationType::Optimization,
437 category: "productivity".to_string(),
438 tags: vec!["workflow".to_string(), "efficiency".to_string()],
439 conditions: vec![
440 RecommendationCondition::SessionDurationBelow { threshold_minutes: 15.0 },
441 ],
442 priority: 6,
443 estimated_benefit: "Increase productivity by 25%".to_string(),
444 });
445
446 templates.push(RecommendationTemplate {
447 id: "ring_calibration".to_string(),
448 title_template: "Calibrate Your Ring".to_string(),
449 description_template: "Your gesture accuracy seems low. Try recalibrating your Haptic Harmony ring for better performance.".to_string(),
450 recommendation_type: RecommendationType::Setting,
451 category: "hardware".to_string(),
452 tags: vec!["calibration".to_string(), "accuracy".to_string()],
453 conditions: vec![
454 RecommendationCondition::GestureAccuracyBelow { threshold: 0.8 },
455 ],
456 priority: 9,
457 estimated_benefit: "Improve gesture accuracy by up to 50%".to_string(),
458 });
459
460 templates.push(RecommendationTemplate {
461 id: "tutorial_advanced".to_string(),
462 title_template: "Learn Advanced Features".to_string(),
463 description_template: "You're using {most_used_feature} frequently. Learn about advanced features that can enhance your experience.".to_string(),
464 recommendation_type: RecommendationType::Tutorial,
465 category: "learning".to_string(),
466 tags: vec!["advanced".to_string(), "features".to_string()],
467 conditions: vec![
468 RecommendationCondition::FeatureUsageAbove { feature: "basic_features".to_string(), threshold: 100 },
469 ],
470 priority: 5,
471 estimated_benefit: "Unlock 10+ advanced features".to_string(),
472 });
473
474 tracing::info!("Initialized {} recommendation templates", templates.len());
475 Ok(())
476 }
477
478 pub async fn record_feedback(
480 &self,
481 user_id: &str,
482 recommendation_id: &str,
483 feedback: RecommendationFeedback,
484 ) -> Result<(), AppError> {
485 let mut patterns = self.user_patterns.write().await;
486
487 if let Some(pattern) = patterns.get_mut(user_id) {
488 let cache = self.recommendations_cache.read().await;
490 if let Some(recommendations) = cache.get(user_id)
491 && let Some(recommendation) =
492 recommendations.iter().find(|r| r.id == recommendation_id)
493 {
494 let category = &recommendation.category;
495 let current_score = pattern.preference_scores.get(category).unwrap_or(&0.5);
496
497 let adjustment = match feedback {
498 RecommendationFeedback::Helpful => 0.1,
499 RecommendationFeedback::NotHelpful => -0.1,
500 RecommendationFeedback::Implemented => 0.2,
501 RecommendationFeedback::Dismissed => -0.05,
502 };
503
504 let new_score = (current_score + adjustment).clamp(0.0, 1.0);
505 pattern
506 .preference_scores
507 .insert(category.clone(), new_score);
508
509 tracing::debug!(
510 "Updated preference score for category '{}' to {:.2} based on feedback",
511 category,
512 new_score
513 );
514 }
515 }
516
517 Ok(())
518 }
519
520 pub async fn get_stats(&self) -> serde_json::Value {
522 let patterns = self.user_patterns.read().await;
523 let cache = self.recommendations_cache.read().await;
524 let templates = self.recommendation_templates.read().await;
525
526 let total_users = patterns.len();
527 let total_cached_recommendations: usize = cache.values().map(|v| v.len()).sum();
528 let total_templates = templates.len();
529
530 serde_json::json!({
531 "total_users": total_users,
532 "total_cached_recommendations": total_cached_recommendations,
533 "total_templates": total_templates,
534 "average_recommendations_per_user": if total_users > 0 {
535 total_cached_recommendations as f64 / total_users as f64
536 } else {
537 0.0
538 }
539 })
540 }
541
542 pub async fn clear_user_data(&self, user_id: &str) -> Result<(), AppError> {
544 let mut patterns = self.user_patterns.write().await;
545 let mut cache = self.recommendations_cache.write().await;
546
547 patterns.remove(user_id);
548 cache.remove(user_id);
549
550 tracing::info!("Cleared recommendation data for user: {}", user_id);
551 Ok(())
552 }
553}
554
555impl Clone for PersonalizedRecommendationEngine {
556 fn clone(&self) -> Self {
557 Self {
558 user_patterns: self.user_patterns.clone(),
559 recommendations_cache: self.recommendations_cache.clone(),
560 global_patterns: self.global_patterns.clone(),
561 config: self.config.clone(),
562 recommendation_templates: self.recommendation_templates.clone(),
563 }
564 }
565}
566
567#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
569pub enum RecommendationFeedback {
570 Helpful,
571 NotHelpful,
572 Implemented,
573 Dismissed,
574}
575
576static RECOMMENDATION_ENGINE: tokio::sync::OnceCell<PersonalizedRecommendationEngine> =
578 tokio::sync::OnceCell::const_new();
579
580pub async fn get_recommendation_engine() -> &'static PersonalizedRecommendationEngine {
582 RECOMMENDATION_ENGINE
583 .get_or_init(|| async {
584 PersonalizedRecommendationEngine::new(RecommendationConfig::default())
585 })
586 .await
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 #[tokio::test]
594 async fn test_recommendation_generation() {
595 let engine = PersonalizedRecommendationEngine::new(RecommendationConfig::default());
596
597 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
599
600 let usage_data = serde_json::json!({
602 "features": {
603 "voice_commands": 10,
604 "gestures": 50
605 },
606 "gestures": {
607 "tap": 30,
608 "swipe": 20
609 }
610 });
611
612 engine
613 .update_user_pattern("user1", usage_data)
614 .await
615 .unwrap();
616
617 let recommendations = engine.generate_recommendations("user1").await.unwrap();
618 assert!(!recommendations.is_empty());
619 }
620
621 #[tokio::test]
622 async fn test_feedback_recording() {
623 let engine = PersonalizedRecommendationEngine::new(RecommendationConfig::default());
624
625 let usage_data = serde_json::json!({
627 "features": {"gestures": 100}
628 });
629
630 engine
631 .update_user_pattern("user1", usage_data)
632 .await
633 .unwrap();
634 let recommendations = engine.generate_recommendations("user1").await.unwrap();
635
636 if let Some(rec) = recommendations.first() {
637 engine
638 .record_feedback("user1", &rec.id, RecommendationFeedback::Helpful)
639 .await
640 .unwrap();
641 }
642
643 let stats = engine.get_stats().await;
645 assert_eq!(stats["total_users"].as_u64().unwrap(), 1);
646 }
647}