Coverage for src / moai_adk / core / user_behavior_analytics.py: 0.00%
376 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1"""
2User Behavior Analytics System
4Analyzes user interactions, patterns, and preferences to provide insights
5for system optimization and user experience improvement.
7Key Features:
8- User interaction tracking and pattern analysis
9- Command usage frequency and preference analysis
10- Session behavior analysis
11- User preference learning and adaptation
12- Collaboration pattern analysis
13- Productivity metrics and insights
14- User experience optimization recommendations
15"""
17import json
18import logging
19import statistics
20import uuid
21from collections import Counter, defaultdict, deque
22from dataclasses import dataclass, field
23from datetime import datetime, timedelta
24from enum import Enum
25from pathlib import Path
26from typing import Any, Dict, List, Optional, Set
28# Set up logging
29logger = logging.getLogger(__name__)
32class UserActionType(Enum):
33 """Types of user actions tracked"""
34 COMMAND_EXECUTION = "command_execution"
35 TOOL_USAGE = "tool_usage"
36 FILE_OPERATION = "file_operation"
37 ERROR_OCCURRED = "error_occurred"
38 HELP_REQUESTED = "help_requested"
39 SESSION_START = "session_start"
40 SESSION_END = "session_end"
41 TASK_COMPLETED = "task_completed"
42 PHASE_TRANSITION = "phase_transition"
45class SessionState(Enum):
46 """User session states"""
47 ACTIVE = "active"
48 IDLE = "idle"
49 FOCUSED = "focused"
50 STRUGGLING = "struggling"
51 PRODUCTIVE = "productive"
52 BREAK = "break"
55@dataclass
56class UserAction:
57 """Single user action event"""
58 timestamp: datetime
59 action_type: UserActionType
60 user_id: str
61 session_id: str
62 action_data: Dict[str, Any]
63 context: Dict[str, Any] = field(default_factory=dict)
64 duration_ms: Optional[float] = None
65 success: bool = True
66 tags: Set[str] = field(default_factory=set)
68 def to_dict(self) -> Dict[str, Any]:
69 """Convert to dictionary for serialization"""
70 return {
71 "timestamp": self.timestamp.isoformat(),
72 "action_type": self.action_type.value,
73 "user_id": self.user_id,
74 "session_id": self.session_id,
75 "action_data": self.action_data,
76 "context": self.context,
77 "duration_ms": self.duration_ms,
78 "success": self.success,
79 "tags": list(self.tags)
80 }
83@dataclass
84class UserSession:
85 """User session information"""
86 session_id: str
87 user_id: str
88 start_time: datetime
89 end_time: Optional[datetime] = None
90 actions: List[UserAction] = field(default_factory=list)
91 state: SessionState = SessionState.ACTIVE
92 productivity_score: float = 0.0
93 total_commands: int = 0
94 total_errors: int = 0
95 total_duration_ms: float = 0.0
96 working_directory: str = ""
97 git_branch: str = ""
98 modified_files: Set[str] = field(default_factory=set)
99 tools_used: Set[str] = field(default_factory=set)
101 def to_dict(self) -> Dict[str, Any]:
102 """Convert to dictionary for serialization"""
103 return {
104 "session_id": self.session_id,
105 "user_id": self.user_id,
106 "start_time": self.start_time.isoformat(),
107 "end_time": self.end_time.isoformat() if self.end_time else None,
108 "state": self.state.value,
109 "productivity_score": self.productivity_score,
110 "total_commands": self.total_commands,
111 "total_errors": self.total_errors,
112 "total_duration_ms": self.total_duration_ms,
113 "working_directory": self.working_directory,
114 "git_branch": self.git_branch,
115 "modified_files": list(self.modified_files),
116 "tools_used": list(self.tools_used)
117 }
120@dataclass
121class UserPreferences:
122 """Learned user preferences and patterns"""
123 user_id: str
124 preferred_commands: Dict[str, int] = field(default_factory=dict)
125 preferred_tools: Dict[str, int] = field(default_factory=dict)
126 working_hours: Dict[str, int] = field(default_factory=dict)
127 peak_productivity_times: List[str] = field(default_factory=list)
128 common_workflows: List[List[str]] = field(default_factory=list)
129 error_patterns: List[str] = field(default_factory=list)
130 success_patterns: List[str] = field(default_factory=list)
131 collaboration_patterns: Dict[str, int] = field(default_factory=dict)
132 last_updated: datetime = field(default_factory=datetime.now)
135class UserBehaviorAnalytics:
136 """Main user behavior analytics system"""
138 def __init__(self, storage_path: Optional[Path] = None):
139 self.storage_path = storage_path or Path.cwd() / ".moai" / "analytics" / "user_behavior"
140 self.storage_path.mkdir(parents=True, exist_ok=True)
142 # Data storage
143 self.user_sessions: Dict[str, UserSession] = {}
144 self.active_sessions: Dict[str, UserSession] = {}
145 self.user_preferences: Dict[str, UserPreferences] = {}
146 self.action_history: deque = deque(maxlen=10000)
148 # Analysis caches
149 self._pattern_cache: Dict[str, Any] = {}
150 self._insight_cache: Dict[str, Any] = {}
151 self._last_cache_update = datetime.now()
153 # Load existing data
154 self._load_data()
156 def track_action(
157 self,
158 action_type: UserActionType,
159 user_id: str,
160 session_id: str,
161 action_data: Dict[str, Any],
162 context: Optional[Dict[str, Any]] = None,
163 duration_ms: Optional[float] = None,
164 success: bool = True
165 ) -> None:
166 """Track a user action"""
167 action = UserAction(
168 timestamp=datetime.now(),
169 action_type=action_type,
170 user_id=user_id,
171 session_id=session_id,
172 action_data=action_data,
173 context=context or {},
174 duration_ms=duration_ms,
175 success=success,
176 tags=self._extract_action_tags(action_type, action_data)
177 )
179 # Store action
180 self.action_history.append(action)
182 # Update session if active
183 if session_id in self.active_sessions:
184 session = self.active_sessions[session_id]
185 session.actions.append(action)
187 # Update session metrics
188 if action_type == UserActionType.COMMAND_EXECUTION:
189 session.total_commands += 1
190 if not success:
191 session.total_errors += 1
193 # Track tools used
194 if 'tool' in action_data:
195 session.tools_used.add(action_data['tool'])
197 # Track file modifications
198 if 'files' in action_data:
199 session.modified_files.update(action_data['files'])
201 if duration_ms:
202 session.total_duration_ms += duration_ms
204 # Update session state
205 session.state = self._analyze_session_state(session)
207 # Update user preferences
208 self._update_user_preferences(user_id, action)
210 # Clear caches periodically
211 if (datetime.now() - self._last_cache_update).seconds > 300:
212 self._pattern_cache.clear()
213 self._insight_cache.clear()
214 self._last_cache_update = datetime.now()
216 def start_session(
217 self,
218 user_id: str,
219 working_directory: str = "",
220 git_branch: str = ""
221 ) -> str:
222 """Start tracking a new user session"""
223 session_id = str(uuid.uuid4())
225 session = UserSession(
226 session_id=session_id,
227 user_id=user_id,
228 start_time=datetime.now(),
229 working_directory=working_directory,
230 git_branch=git_branch,
231 state=SessionState.ACTIVE
232 )
234 self.active_sessions[session_id] = session
235 self.user_sessions[session_id] = session
237 # Track session start action
238 self.track_action(
239 UserActionType.SESSION_START,
240 user_id,
241 session_id,
242 {"working_directory": working_directory, "git_branch": git_branch}
243 )
245 return session_id
247 def end_session(self, session_id: str) -> Optional[UserSession]:
248 """End a user session"""
249 if session_id not in self.active_sessions:
250 return None
252 session = self.active_sessions[session_id]
253 session.end_time = datetime.now()
255 # Calculate final productivity score
256 session.productivity_score = self._calculate_productivity_score(session)
258 # Move from active to completed sessions
259 del self.active_sessions[session_id]
261 # Track session end action
262 self.track_action(
263 UserActionType.SESSION_END,
264 session.user_id,
265 session_id,
266 {
267 "duration_ms": session.total_duration_ms,
268 "commands": session.total_commands,
269 "errors": session.total_errors,
270 "productivity_score": session.productivity_score
271 }
272 )
274 # Save data periodically
275 self._save_data()
277 return session
279 def get_user_patterns(
280 self,
281 user_id: str,
282 days: int = 30
283 ) -> Dict[str, Any]:
284 """Get user behavior patterns and insights"""
285 cache_key = f"patterns_{user_id}_{days}"
287 if cache_key in self._pattern_cache:
288 return self._pattern_cache[cache_key]
290 cutoff_date = datetime.now() - timedelta(days=days)
292 # Get user sessions in the time range
293 user_sessions = [
294 session for session in self.user_sessions.values()
295 if session.user_id == user_id and session.start_time >= cutoff_date
296 ]
298 # Analyze patterns
299 patterns = {
300 "session_count": len(user_sessions),
301 "avg_session_duration": 0.0,
302 "avg_productivity_score": 0.0,
303 "most_used_commands": {},
304 "most_used_tools": {},
305 "peak_productivity_hours": [],
306 "common_workflows": [],
307 "error_patterns": [],
308 "working_preferences": {}
309 }
311 if user_sessions:
312 # Session metrics
313 durations = [s.total_duration_ms for s in user_sessions if s.total_duration_ms > 0]
314 productivity_scores = [s.productivity_score for s in user_sessions if s.productivity_score > 0]
316 if durations:
317 patterns["avg_session_duration"] = statistics.mean(durations)
318 if productivity_scores:
319 patterns["avg_productivity_score"] = statistics.mean(productivity_scores)
321 # Command usage
322 all_commands = []
323 all_tools = []
325 for session in user_sessions:
326 for action in session.actions:
327 if action.action_type == UserActionType.COMMAND_EXECUTION:
328 if 'command' in action.action_data:
329 all_commands.append(action.action_data['command'])
330 if 'tool' in action.action_data:
331 all_tools.append(action.action_data['tool'])
333 patterns["most_used_commands"] = dict(Counter(all_commands).most_common(10))
334 patterns["most_used_tools"] = dict(Counter(all_tools).most_common(10))
336 # Peak productivity hours
337 hour_productivity = defaultdict(list)
339 for session in user_sessions:
340 hour_productivity[session.start_time.hour].append(session.productivity_score)
342 avg_hourly_productivity = {
343 hour: statistics.mean(scores) if scores else 0
344 for hour, scores in hour_productivity.items()
345 }
347 # Top 3 most productive hours
348 top_hours = sorted(
349 avg_hourly_productivity.items(),
350 key=lambda x: x[1],
351 reverse=True
352 )[:3]
353 patterns["peak_productivity_hours"] = [f"{hour:02d}:00" for hour, _ in top_hours]
355 # Cache results
356 self._pattern_cache[cache_key] = patterns
358 return patterns
360 def get_user_insights(
361 self,
362 user_id: str,
363 days: int = 7
364 ) -> Dict[str, Any]:
365 """Get actionable insights and recommendations for a user"""
366 cache_key = f"insights_{user_id}_{days}"
368 if cache_key in self._insight_cache:
369 return self._insight_cache[cache_key]
371 patterns = self.get_user_patterns(user_id, days)
373 insights = {
374 "productivity_insights": [],
375 "efficiency_recommendations": [],
376 "collaboration_insights": [],
377 "learning_opportunities": [],
378 "tool_recommendations": [],
379 "workflow_optimizations": []
380 }
382 # Productivity insights
383 if patterns["avg_productivity_score"] > 80:
384 insights["productivity_insights"].append(
385 "Excellent productivity score! User consistently performs at high level."
386 )
387 elif patterns["avg_productivity_score"] < 50:
388 insights["productivity_insights"].append(
389 "Productivity score could be improved. Consider workflow optimization."
390 )
392 # Session duration insights
393 if patterns["avg_session_duration"] > 1800000: # > 30 minutes
394 insights["productivity_insights"].append(
395 "Long session durations detected. Consider taking regular breaks for sustained productivity."
396 )
398 # Error pattern analysis
399 recent_actions = [
400 action for action in self.action_history
401 if action.user_id == user_id and
402 action.timestamp >= datetime.now() - timedelta(days=days)
403 ]
405 error_actions = [
406 action for action in recent_actions
407 if action.action_type == UserActionType.ERROR_OCCURRED or not action.success
408 ]
410 if len(error_actions) > len(recent_actions) * 0.1: # > 10% error rate
411 insights["efficiency_recommendations"].append(
412 "High error rate detected. Consider additional training or tool familiarization."
413 )
415 # Tool usage insights
416 if patterns["most_used_tools"]:
417 top_tool = max(patterns["most_used_tools"].items(), key=lambda x: x[1])
418 insights["tool_recommendations"].append(
419 f"Most frequently used tool: {top_tool[0]} ({top_tool[1]} uses)"
420 )
422 # Command pattern insights
423 if patterns["most_used_commands"]:
424 commands = list(patterns["most_used_commands"].keys())
425 if "/moai:" in " ".join(commands[:3]): # Top 3 commands
426 insights["workflow_optimizations"].append(
427 "Heavy MoAI command usage detected. Consider learning keyboard shortcuts for faster workflow."
428 )
430 # Peak productivity insights
431 if patterns["peak_productivity_hours"]:
432 peak_hours = ", ".join(patterns["peak_productivity_hours"])
433 insights["efficiency_recommendations"].append(
434 f"Peak productivity hours: {peak_hours}. Schedule important work during these times."
435 )
437 # Learning opportunities
438 if patterns["session_count"] < 5:
439 insights["learning_opportunities"].append(
440 "Consider more frequent sessions to build momentum and consistency."
441 )
443 # Cache results
444 self._insight_cache[cache_key] = insights
446 return insights
448 def get_team_analytics(
449 self,
450 days: int = 30
451 ) -> Dict[str, Any]:
452 """Get team-wide analytics and collaboration patterns"""
453 cutoff_date = datetime.now() - timedelta(days=days)
455 # Get all sessions in time range
456 recent_sessions = [
457 session for session in self.user_sessions.values()
458 if session.start_time >= cutoff_date
459 ]
461 # Aggregate metrics
462 team_metrics = {
463 "total_sessions": len(recent_sessions),
464 "unique_users": len(set(s.user_id for s in recent_sessions)),
465 "avg_session_duration": 0.0,
466 "avg_productivity_score": 0.0,
467 "most_active_users": {},
468 "most_used_tools": {},
469 "collaboration_patterns": {},
470 "productivity_distribution": {},
471 "peak_hours": {}
472 }
474 if recent_sessions:
475 # Calculate averages
476 durations = [s.total_duration_ms for s in recent_sessions if s.total_duration_ms > 0]
477 scores = [s.productivity_score for s in recent_sessions if s.productivity_score > 0]
479 if durations:
480 team_metrics["avg_session_duration"] = statistics.mean(durations)
481 if scores:
482 team_metrics["avg_productivity_score"] = statistics.mean(scores)
484 # Most active users
485 user_session_counts = Counter(s.user_id for s in recent_sessions)
486 team_metrics["most_active_users"] = dict(user_session_counts.most_common(10))
488 # Most used tools across team
489 all_tools = []
490 for session in recent_sessions:
491 all_tools.extend(session.tools_used)
493 team_metrics["most_used_tools"] = dict(Counter(all_tools).most_common(10))
495 # Productivity distribution
496 productivity_ranges = {
497 "0-20": 0, "21-40": 0, "41-60": 0,
498 "61-80": 0, "81-100": 0
499 }
501 for score in scores:
502 if score <= 20:
503 productivity_ranges["0-20"] += 1
504 elif score <= 40:
505 productivity_ranges["21-40"] += 1
506 elif score <= 60:
507 productivity_ranges["41-60"] += 1
508 elif score <= 80:
509 productivity_ranges["61-80"] += 1
510 else:
511 productivity_ranges["81-100"] += 1
513 team_metrics["productivity_distribution"] = productivity_ranges
515 return team_metrics
517 def _extract_action_tags(self, action_type: UserActionType, action_data: Dict[str, Any]) -> Set[str]:
518 """Extract relevant tags from action data"""
519 tags = {action_type.value}
521 # Extract tool tags
522 if 'tool' in action_data:
523 tags.add(f"tool:{action_data['tool']}")
525 # Extract command tags
526 if 'command' in action_data:
527 command = action_data['command']
528 if '/moai:' in command:
529 tags.add("moai_command")
530 if 'git' in command.lower():
531 tags.add("git_operation")
533 # Extract phase tags
534 if 'phase' in action_data:
535 tags.add(f"phase:{action_data['phase']}")
537 # Extract success/failure tags
538 if 'success' in action_data:
539 tags.add("success" if action_data['success'] else "failure")
541 return tags
543 def _update_user_preferences(self, user_id: str, action: UserAction) -> None:
544 """Update learned user preferences based on actions"""
545 if user_id not in self.user_preferences:
546 self.user_preferences[user_id] = UserPreferences(user_id=user_id)
548 prefs = self.user_preferences[user_id]
550 # Update command preferences
551 if action.action_type == UserActionType.COMMAND_EXECUTION:
552 command = action.action_data.get('command', '')
553 if command:
554 prefs.preferred_commands[command] = prefs.preferred_commands.get(command, 0) + 1
556 # Update tool preferences
557 if 'tool' in action.action_data:
558 tool = action.action_data['tool']
559 prefs.preferred_tools[tool] = prefs.preferred_tools.get(tool, 0) + 1
561 # Update working hours
562 hour = action.timestamp.hour
563 prefs.working_hours[str(hour)] = prefs.working_hours.get(str(hour), 0) + 1
565 # Update patterns based on success/failure
566 if action.success:
567 pattern_key = f"{action.action_type.value}_{action.action_data}"
568 if pattern_key not in prefs.success_patterns:
569 prefs.success_patterns.append(pattern_key)
570 else:
571 pattern_key = f"{action.action_type.value}_{action.action_data}"
572 if pattern_key not in prefs.error_patterns:
573 prefs.error_patterns.append(pattern_key)
575 prefs.last_updated = datetime.now()
577 def _analyze_session_state(self, session: UserSession) -> SessionState:
578 """Analyze and determine current session state"""
579 if not session.actions:
580 return SessionState.ACTIVE
582 recent_actions = [
583 action for action in session.actions[-10:] # Last 10 actions
584 if (datetime.now() - action.timestamp).seconds < 300 # Last 5 minutes
585 ]
587 if not recent_actions:
588 return SessionState.IDLE
590 # Calculate metrics
591 error_rate = sum(1 for action in recent_actions if not action.success) / len(recent_actions)
593 durations_with_ms = [a.duration_ms for a in recent_actions if a.duration_ms is not None]
594 avg_duration = statistics.mean(durations_with_ms) if durations_with_ms else 0
596 # Determine state
597 if error_rate > 0.5:
598 return SessionState.STRUGGLING
599 elif avg_duration > 10000 and error_rate < 0.1:
600 return SessionState.PRODUCTIVE
601 elif error_rate < 0.1 and len(recent_actions) > 5:
602 return SessionState.FOCUSED
603 else:
604 return SessionState.ACTIVE
606 def _calculate_productivity_score(self, session: UserSession) -> float:
607 """Calculate productivity score for a session (0-100)"""
608 if not session.actions:
609 return 0.0
611 # Base score from success rate
612 successful_actions = sum(1 for action in session.actions if action.success)
613 success_rate = successful_actions / len(session.actions)
614 base_score = success_rate * 50
616 # Duration factor (optimal session duration)
617 duration_hours = session.total_duration_ms / (1000 * 60 * 60)
618 duration_factor = 0
620 if 0.5 <= duration_hours <= 4: # Optimal 30 min to 4 hours
621 duration_factor = 25
622 elif duration_hours > 0.25: # At least 15 minutes
623 duration_factor = 15
625 # Tool diversity factor
626 tool_diversity = min(len(session.tools_used) / 10, 1) * 15
628 # File modifications factor (if it's a coding session)
629 file_factor = 0
630 if session.modified_files:
631 file_factor = min(len(session.modified_files) / 5, 1) * 10
633 total_score = base_score + duration_factor + tool_diversity + file_factor
634 return min(total_score, 100.0)
636 def _load_data(self) -> None:
637 """Load stored analytics data"""
638 try:
639 # Load user preferences
640 prefs_file = self.storage_path / "user_preferences.json"
641 if prefs_file.exists():
642 with open(prefs_file, 'r') as f:
643 prefs_data = json.load(f)
645 for user_id, prefs_dict in prefs_data.items():
646 prefs = UserPreferences(user_id=user_id)
647 prefs.preferred_commands = prefs_dict.get('preferred_commands', {})
648 prefs.preferred_tools = prefs_dict.get('preferred_tools', {})
649 prefs.working_hours = prefs_dict.get('working_hours', {})
650 prefs.peak_productivity_times = prefs_dict.get('peak_productivity_times', [])
651 prefs.common_workflows = prefs_dict.get('common_workflows', [])
652 prefs.error_patterns = prefs_dict.get('error_patterns', [])
653 prefs.success_patterns = prefs_dict.get('success_patterns', [])
654 prefs.collaboration_patterns = prefs_dict.get('collaboration_patterns', {})
656 if prefs_dict.get('last_updated'):
657 prefs.last_updated = datetime.fromisoformat(prefs_dict['last_updated'])
659 self.user_preferences[user_id] = prefs
661 logger.info(f"Loaded preferences for {len(self.user_preferences)} users")
663 except Exception as e:
664 logger.error(f"Error loading analytics data: {e}")
666 def _save_data(self) -> None:
667 """Save analytics data to storage"""
668 try:
669 # Save user preferences
670 prefs_data = {}
671 for user_id, prefs in self.user_preferences.items():
672 prefs_data[user_id] = {
673 'preferred_commands': prefs.preferred_commands,
674 'preferred_tools': prefs.preferred_tools,
675 'working_hours': prefs.working_hours,
676 'peak_productivity_times': prefs.peak_productivity_times,
677 'common_workflows': prefs.common_workflows,
678 'error_patterns': prefs.error_patterns,
679 'success_patterns': prefs.success_patterns,
680 'collaboration_patterns': prefs.collaboration_patterns,
681 'last_updated': prefs.last_updated.isoformat()
682 }
684 prefs_file = self.storage_path / "user_preferences.json"
685 with open(prefs_file, 'w') as f:
686 json.dump(prefs_data, f, indent=2)
688 logger.info(f"Saved preferences for {len(self.user_preferences)} users")
690 except Exception as e:
691 logger.error(f"Error saving analytics data: {e}")
693 def export_data(self, output_path: Path, user_id: Optional[str] = None) -> bool:
694 """Export analytics data to file"""
695 try:
696 export_data = {
697 "export_timestamp": datetime.now().isoformat(),
698 "user_id": user_id or "all_users",
699 "data": {}
700 }
702 if user_id:
703 # Export single user data
704 patterns = self.get_user_patterns(user_id)
705 insights = self.get_user_insights(user_id)
706 prefs = self.user_preferences.get(user_id)
708 export_data["data"] = {
709 "patterns": patterns,
710 "insights": insights,
711 "preferences": prefs.to_dict() if prefs else None
712 }
713 else:
714 # Export team data
715 export_data["data"] = {
716 "team_analytics": self.get_team_analytics(),
717 "user_count": len(self.user_preferences),
718 "session_count": len(self.user_sessions)
719 }
721 with open(output_path, 'w') as f:
722 json.dump(export_data, f, indent=2)
724 return True
726 except Exception as e:
727 logger.error(f"Error exporting data: {e}")
728 return False
730 def get_realtime_metrics(self, user_id: Optional[str] = None) -> Dict[str, Any]:
731 """Get real-time analytics metrics"""
732 current_time = datetime.now()
734 metrics = {
735 "timestamp": current_time.isoformat(),
736 "active_sessions": len(self.active_sessions),
737 "total_sessions_today": 0,
738 "avg_session_duration": 0.0,
739 "current_productivity_scores": []
740 }
742 today_start = current_time.replace(hour=0, minute=0, second=0, microsecond=0)
744 # Filter sessions and calculate metrics
745 today_sessions = [
746 session for session in self.user_sessions.values()
747 if session.start_time >= today_start
748 ]
750 metrics["total_sessions_today"] = len(today_sessions)
752 if today_sessions:
753 durations = [s.total_duration_ms for s in today_sessions if s.total_duration_ms > 0]
754 if durations:
755 metrics["avg_session_duration"] = statistics.mean(durations)
757 # Current productivity scores
758 for session in self.active_sessions.values():
759 if user_id is None or session.user_id == user_id:
760 current_score = self._calculate_productivity_score(session)
761 metrics["current_productivity_scores"].append({
762 "user_id": session.user_id,
763 "session_id": session.session_id,
764 "score": current_score,
765 "state": session.state.value
766 })
768 return metrics
771# Global instance for easy access
772_user_analytics: Optional[UserBehaviorAnalytics] = None
775def get_user_analytics() -> UserBehaviorAnalytics:
776 """Get or create global user behavior analytics instance"""
777 global _user_analytics
778 if _user_analytics is None:
779 _user_analytics = UserBehaviorAnalytics()
780 return _user_analytics
783# Convenience functions
784def track_user_action(
785 action_type: UserActionType,
786 user_id: str,
787 session_id: str,
788 action_data: Dict[str, Any],
789 context: Optional[Dict[str, Any]] = None,
790 duration_ms: Optional[float] = None,
791 success: bool = True
792) -> None:
793 """Track a user action"""
794 analytics = get_user_analytics()
795 analytics.track_action(
796 action_type, user_id, session_id, action_data,
797 context, duration_ms, success
798 )
801def start_user_session(user_id: str, working_directory: str = "", git_branch: str = "") -> str:
802 """Start tracking a user session"""
803 analytics = get_user_analytics()
804 return analytics.start_session(user_id, working_directory, git_branch)
807def end_user_session(session_id: str) -> Optional[UserSession]:
808 """End a user session"""
809 analytics = get_user_analytics()
810 return analytics.end_session(session_id)
813def get_user_patterns(user_id: str, days: int = 30) -> Dict[str, Any]:
814 """Get user behavior patterns"""
815 analytics = get_user_analytics()
816 return analytics.get_user_patterns(user_id, days)
819def get_user_insights(user_id: str, days: int = 7) -> Dict[str, Any]:
820 """Get user insights and recommendations"""
821 analytics = get_user_analytics()
822 return analytics.get_user_insights(user_id, days)
825if __name__ == "__main__":
826 # Example usage
827 print("Testing User Behavior Analytics...")
829 analytics = UserBehaviorAnalytics()
831 # Simulate user session
832 user_id = "test_user"
833 session_id = analytics.start_session(user_id, "/Users/goos/MoAI/MoAI-ADK", "main")
835 # Track some actions
836 analytics.track_action(
837 UserActionType.COMMAND_EXECUTION,
838 user_id,
839 session_id,
840 {"command": "/moai:1-plan test feature", "tool": "spec_builder"},
841 duration_ms=1500,
842 success=True
843 )
845 analytics.track_action(
846 UserActionType.TOOL_USAGE,
847 user_id,
848 session_id,
849 {"tool": "git", "operation": "commit"},
850 duration_ms=800,
851 success=True
852 )
854 # End session
855 ended_session = analytics.end_session(session_id)
857 if ended_session:
858 print("Session completed:")
859 print(f" Duration: {ended_session.total_duration_ms / 1000:.1f} seconds")
860 print(f" Commands: {ended_session.total_commands}")
861 print(f" Productivity Score: {ended_session.productivity_score}")
862 print(f" Tools Used: {list(ended_session.tools_used)}")
864 # Get patterns
865 patterns = analytics.get_user_patterns(user_id, days=1)
866 print(f"User patterns: {patterns}")
868 print("User Behavior Analytics test completed!")