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 conditions_met += 1;
317 confidence += 0.15;
318 }
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 }
330 RecommendationCondition::HasNotUsedFeature { feature } => {
331 if !user_pattern.feature_usage.contains_key(feature) {
332 conditions_met += 1;
333 confidence += 0.2;
334 }
335 }
336 RecommendationCondition::UsesFeatureFrequently { feature, min_usage } => {
337 let usage = user_pattern.feature_usage.get(feature).unwrap_or(&0);
338 if *usage >= *min_usage {
339 conditions_met += 1;
340 confidence += 0.1;
341 }
342 }
343 _ => {} }
345 }
346
347 if total_conditions > 0 && (conditions_met as f32 / total_conditions as f32) < 0.5 {
349 return None;
350 }
351
352 if let Some(pref_score) = user_pattern.preference_scores.get(&template.category) {
354 confidence *= pref_score;
355 }
356
357 Some(Recommendation {
358 id: uuid::Uuid::new_v4().to_string(),
359 title: self.personalize_text(&template.title_template, user_pattern),
360 description: self.personalize_text(&template.description_template, user_pattern),
361 recommendation_type: template.recommendation_type.clone(),
362 confidence: confidence.min(1.0),
363 priority: template.priority,
364 category: template.category.clone(),
365 tags: template.tags.clone(),
366 action_url: None,
367 estimated_benefit: template.estimated_benefit.clone(),
368 created_at: chrono::Utc::now(),
369 })
370 }
371
372 fn personalize_text(&self, template: &str, user_pattern: &UserBehaviorPattern) -> String {
374 let mut result = template.to_string();
375
376 if let Some(most_used) = user_pattern.session_patterns.most_used_features.first() {
378 result = result.replace("{most_used_feature}", most_used);
379 }
380
381 result = result.replace(
382 "{session_duration}",
383 &format!(
384 "{:.1}",
385 user_pattern
386 .session_patterns
387 .average_session_duration_minutes
388 ),
389 );
390
391 result = result.replace(
392 "{error_rate}",
393 &format!("{:.1}%", user_pattern.session_patterns.error_rate * 100.0),
394 );
395
396 result
397 }
398
399 async fn initialize_default_templates(&self) -> Result<(), AppError> {
401 let mut templates = self.recommendation_templates.write().await;
402
403 templates.push(RecommendationTemplate {
404 id: "voice_training".to_string(),
405 title_template: "Improve Voice Recognition Accuracy".to_string(),
406 description_template: "Your voice recognition accuracy could be improved. Try the voice training feature to personalize the system to your voice.".to_string(),
407 recommendation_type: RecommendationType::Feature,
408 category: "voice".to_string(),
409 tags: vec!["accuracy".to_string(), "training".to_string()],
410 conditions: vec![
411 RecommendationCondition::HasNotUsedFeature { feature: "voice_training".to_string() },
412 RecommendationCondition::ErrorRateAbove { threshold: 0.1 },
413 ],
414 priority: 8,
415 estimated_benefit: "Up to 30% improvement in voice recognition accuracy".to_string(),
416 });
417
418 templates.push(RecommendationTemplate {
419 id: "gesture_customization".to_string(),
420 title_template: "Customize Your Gestures".to_string(),
421 description_template: "You use gestures frequently! Create custom gestures for your most common actions to save time.".to_string(),
422 recommendation_type: RecommendationType::Feature,
423 category: "gestures".to_string(),
424 tags: vec!["customization".to_string(), "efficiency".to_string()],
425 conditions: vec![
426 RecommendationCondition::UsesFeatureFrequently { feature: "gestures".to_string(), min_usage: 50 },
427 RecommendationCondition::HasNotUsedFeature { feature: "custom_gestures".to_string() },
428 ],
429 priority: 7,
430 estimated_benefit: "Reduce gesture time by up to 40%".to_string(),
431 });
432
433 templates.push(RecommendationTemplate {
434 id: "session_optimization".to_string(),
435 title_template: "Optimize Your Workflow".to_string(),
436 description_template: "Your sessions are shorter than average ({session_duration} min). Try these workflow optimizations to be more productive.".to_string(),
437 recommendation_type: RecommendationType::Optimization,
438 category: "productivity".to_string(),
439 tags: vec!["workflow".to_string(), "efficiency".to_string()],
440 conditions: vec![
441 RecommendationCondition::SessionDurationBelow { threshold_minutes: 15.0 },
442 ],
443 priority: 6,
444 estimated_benefit: "Increase productivity by 25%".to_string(),
445 });
446
447 templates.push(RecommendationTemplate {
448 id: "ring_calibration".to_string(),
449 title_template: "Calibrate Your Ring".to_string(),
450 description_template: "Your gesture accuracy seems low. Try recalibrating your Haptic Harmony ring for better performance.".to_string(),
451 recommendation_type: RecommendationType::Setting,
452 category: "hardware".to_string(),
453 tags: vec!["calibration".to_string(), "accuracy".to_string()],
454 conditions: vec![
455 RecommendationCondition::GestureAccuracyBelow { threshold: 0.8 },
456 ],
457 priority: 9,
458 estimated_benefit: "Improve gesture accuracy by up to 50%".to_string(),
459 });
460
461 templates.push(RecommendationTemplate {
462 id: "tutorial_advanced".to_string(),
463 title_template: "Learn Advanced Features".to_string(),
464 description_template: "You're using {most_used_feature} frequently. Learn about advanced features that can enhance your experience.".to_string(),
465 recommendation_type: RecommendationType::Tutorial,
466 category: "learning".to_string(),
467 tags: vec!["advanced".to_string(), "features".to_string()],
468 conditions: vec![
469 RecommendationCondition::FeatureUsageAbove { feature: "basic_features".to_string(), threshold: 100 },
470 ],
471 priority: 5,
472 estimated_benefit: "Unlock 10+ advanced features".to_string(),
473 });
474
475 tracing::info!("Initialized {} recommendation templates", templates.len());
476 Ok(())
477 }
478
479 pub async fn record_feedback(
481 &self,
482 user_id: &str,
483 recommendation_id: &str,
484 feedback: RecommendationFeedback,
485 ) -> Result<(), AppError> {
486 let mut patterns = self.user_patterns.write().await;
487
488 if let Some(pattern) = patterns.get_mut(user_id) {
489 let cache = self.recommendations_cache.read().await;
491 if let Some(recommendations) = cache.get(user_id)
492 && let Some(recommendation) =
493 recommendations.iter().find(|r| r.id == recommendation_id)
494 {
495 let category = &recommendation.category;
496 let current_score = pattern.preference_scores.get(category).unwrap_or(&0.5);
497
498 let adjustment = match feedback {
499 RecommendationFeedback::Helpful => 0.1,
500 RecommendationFeedback::NotHelpful => -0.1,
501 RecommendationFeedback::Implemented => 0.2,
502 RecommendationFeedback::Dismissed => -0.05,
503 };
504
505 let new_score = (current_score + adjustment).clamp(0.0, 1.0);
506 pattern
507 .preference_scores
508 .insert(category.clone(), new_score);
509
510 tracing::debug!(
511 "Updated preference score for category '{}' to {:.2} based on feedback",
512 category,
513 new_score
514 );
515 }
516 }
517
518 Ok(())
519 }
520
521 pub async fn get_stats(&self) -> serde_json::Value {
523 let patterns = self.user_patterns.read().await;
524 let cache = self.recommendations_cache.read().await;
525 let templates = self.recommendation_templates.read().await;
526
527 let total_users = patterns.len();
528 let total_cached_recommendations: usize = cache.values().map(|v| v.len()).sum();
529 let total_templates = templates.len();
530
531 serde_json::json!({
532 "total_users": total_users,
533 "total_cached_recommendations": total_cached_recommendations,
534 "total_templates": total_templates,
535 "average_recommendations_per_user": if total_users > 0 {
536 total_cached_recommendations as f64 / total_users as f64
537 } else {
538 0.0
539 }
540 })
541 }
542
543 pub async fn clear_user_data(&self, user_id: &str) -> Result<(), AppError> {
545 let mut patterns = self.user_patterns.write().await;
546 let mut cache = self.recommendations_cache.write().await;
547
548 patterns.remove(user_id);
549 cache.remove(user_id);
550
551 tracing::info!("Cleared recommendation data for user: {}", user_id);
552 Ok(())
553 }
554}
555
556impl Clone for PersonalizedRecommendationEngine {
557 fn clone(&self) -> Self {
558 Self {
559 user_patterns: self.user_patterns.clone(),
560 recommendations_cache: self.recommendations_cache.clone(),
561 global_patterns: self.global_patterns.clone(),
562 config: self.config.clone(),
563 recommendation_templates: self.recommendation_templates.clone(),
564 }
565 }
566}
567
568#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
570pub enum RecommendationFeedback {
571 Helpful,
572 NotHelpful,
573 Implemented,
574 Dismissed,
575}
576
577static RECOMMENDATION_ENGINE: tokio::sync::OnceCell<PersonalizedRecommendationEngine> =
579 tokio::sync::OnceCell::const_new();
580
581pub async fn get_recommendation_engine() -> &'static PersonalizedRecommendationEngine {
583 RECOMMENDATION_ENGINE
584 .get_or_init(|| async {
585 PersonalizedRecommendationEngine::new(RecommendationConfig::default())
586 })
587 .await
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[tokio::test]
595 async fn test_recommendation_generation() {
596 let engine = PersonalizedRecommendationEngine::new(RecommendationConfig::default());
597
598 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
600
601 let usage_data = serde_json::json!({
603 "features": {
604 "voice_commands": 10,
605 "gestures": 50
606 },
607 "gestures": {
608 "tap": 30,
609 "swipe": 20
610 }
611 });
612
613 engine
614 .update_user_pattern("user1", usage_data)
615 .await
616 .unwrap();
617
618 let recommendations = engine.generate_recommendations("user1").await.unwrap();
619 assert!(!recommendations.is_empty());
620 }
621
622 #[tokio::test]
623 async fn test_feedback_recording() {
624 let engine = PersonalizedRecommendationEngine::new(RecommendationConfig::default());
625
626 let usage_data = serde_json::json!({
628 "features": {"gestures": 100}
629 });
630
631 engine
632 .update_user_pattern("user1", usage_data)
633 .await
634 .unwrap();
635 let recommendations = engine.generate_recommendations("user1").await.unwrap();
636
637 if let Some(rec) = recommendations.first() {
638 engine
639 .record_feedback("user1", &rec.id, RecommendationFeedback::Helpful)
640 .await
641 .unwrap();
642 }
643
644 let stats = engine.get_stats().await;
646 assert_eq!(stats["total_users"].as_u64().unwrap(), 1);
647 }
648}