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

1""" 

2User Behavior Analytics System 

3 

4Analyzes user interactions, patterns, and preferences to provide insights 

5for system optimization and user experience improvement. 

6 

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""" 

16 

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 

27 

28# Set up logging 

29logger = logging.getLogger(__name__) 

30 

31 

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" 

43 

44 

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" 

53 

54 

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) 

67 

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 } 

81 

82 

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) 

100 

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 } 

118 

119 

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) 

133 

134 

135class UserBehaviorAnalytics: 

136 """Main user behavior analytics system""" 

137 

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) 

141 

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) 

147 

148 # Analysis caches 

149 self._pattern_cache: Dict[str, Any] = {} 

150 self._insight_cache: Dict[str, Any] = {} 

151 self._last_cache_update = datetime.now() 

152 

153 # Load existing data 

154 self._load_data() 

155 

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 ) 

178 

179 # Store action 

180 self.action_history.append(action) 

181 

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) 

186 

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 

192 

193 # Track tools used 

194 if 'tool' in action_data: 

195 session.tools_used.add(action_data['tool']) 

196 

197 # Track file modifications 

198 if 'files' in action_data: 

199 session.modified_files.update(action_data['files']) 

200 

201 if duration_ms: 

202 session.total_duration_ms += duration_ms 

203 

204 # Update session state 

205 session.state = self._analyze_session_state(session) 

206 

207 # Update user preferences 

208 self._update_user_preferences(user_id, action) 

209 

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() 

215 

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()) 

224 

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 ) 

233 

234 self.active_sessions[session_id] = session 

235 self.user_sessions[session_id] = session 

236 

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 ) 

244 

245 return session_id 

246 

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 

251 

252 session = self.active_sessions[session_id] 

253 session.end_time = datetime.now() 

254 

255 # Calculate final productivity score 

256 session.productivity_score = self._calculate_productivity_score(session) 

257 

258 # Move from active to completed sessions 

259 del self.active_sessions[session_id] 

260 

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 ) 

273 

274 # Save data periodically 

275 self._save_data() 

276 

277 return session 

278 

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}" 

286 

287 if cache_key in self._pattern_cache: 

288 return self._pattern_cache[cache_key] 

289 

290 cutoff_date = datetime.now() - timedelta(days=days) 

291 

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 ] 

297 

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 } 

310 

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] 

315 

316 if durations: 

317 patterns["avg_session_duration"] = statistics.mean(durations) 

318 if productivity_scores: 

319 patterns["avg_productivity_score"] = statistics.mean(productivity_scores) 

320 

321 # Command usage 

322 all_commands = [] 

323 all_tools = [] 

324 

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']) 

332 

333 patterns["most_used_commands"] = dict(Counter(all_commands).most_common(10)) 

334 patterns["most_used_tools"] = dict(Counter(all_tools).most_common(10)) 

335 

336 # Peak productivity hours 

337 hour_productivity = defaultdict(list) 

338 

339 for session in user_sessions: 

340 hour_productivity[session.start_time.hour].append(session.productivity_score) 

341 

342 avg_hourly_productivity = { 

343 hour: statistics.mean(scores) if scores else 0 

344 for hour, scores in hour_productivity.items() 

345 } 

346 

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] 

354 

355 # Cache results 

356 self._pattern_cache[cache_key] = patterns 

357 

358 return patterns 

359 

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}" 

367 

368 if cache_key in self._insight_cache: 

369 return self._insight_cache[cache_key] 

370 

371 patterns = self.get_user_patterns(user_id, days) 

372 

373 insights = { 

374 "productivity_insights": [], 

375 "efficiency_recommendations": [], 

376 "collaboration_insights": [], 

377 "learning_opportunities": [], 

378 "tool_recommendations": [], 

379 "workflow_optimizations": [] 

380 } 

381 

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 ) 

391 

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 ) 

397 

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 ] 

404 

405 error_actions = [ 

406 action for action in recent_actions 

407 if action.action_type == UserActionType.ERROR_OCCURRED or not action.success 

408 ] 

409 

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 ) 

414 

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 ) 

421 

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 ) 

429 

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 ) 

436 

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 ) 

442 

443 # Cache results 

444 self._insight_cache[cache_key] = insights 

445 

446 return insights 

447 

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) 

454 

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 ] 

460 

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 } 

473 

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] 

478 

479 if durations: 

480 team_metrics["avg_session_duration"] = statistics.mean(durations) 

481 if scores: 

482 team_metrics["avg_productivity_score"] = statistics.mean(scores) 

483 

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)) 

487 

488 # Most used tools across team 

489 all_tools = [] 

490 for session in recent_sessions: 

491 all_tools.extend(session.tools_used) 

492 

493 team_metrics["most_used_tools"] = dict(Counter(all_tools).most_common(10)) 

494 

495 # Productivity distribution 

496 productivity_ranges = { 

497 "0-20": 0, "21-40": 0, "41-60": 0, 

498 "61-80": 0, "81-100": 0 

499 } 

500 

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 

512 

513 team_metrics["productivity_distribution"] = productivity_ranges 

514 

515 return team_metrics 

516 

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} 

520 

521 # Extract tool tags 

522 if 'tool' in action_data: 

523 tags.add(f"tool:{action_data['tool']}") 

524 

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") 

532 

533 # Extract phase tags 

534 if 'phase' in action_data: 

535 tags.add(f"phase:{action_data['phase']}") 

536 

537 # Extract success/failure tags 

538 if 'success' in action_data: 

539 tags.add("success" if action_data['success'] else "failure") 

540 

541 return tags 

542 

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) 

547 

548 prefs = self.user_preferences[user_id] 

549 

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 

555 

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 

560 

561 # Update working hours 

562 hour = action.timestamp.hour 

563 prefs.working_hours[str(hour)] = prefs.working_hours.get(str(hour), 0) + 1 

564 

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) 

574 

575 prefs.last_updated = datetime.now() 

576 

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 

581 

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 ] 

586 

587 if not recent_actions: 

588 return SessionState.IDLE 

589 

590 # Calculate metrics 

591 error_rate = sum(1 for action in recent_actions if not action.success) / len(recent_actions) 

592 

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 

595 

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 

605 

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 

610 

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 

615 

616 # Duration factor (optimal session duration) 

617 duration_hours = session.total_duration_ms / (1000 * 60 * 60) 

618 duration_factor = 0 

619 

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 

624 

625 # Tool diversity factor 

626 tool_diversity = min(len(session.tools_used) / 10, 1) * 15 

627 

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 

632 

633 total_score = base_score + duration_factor + tool_diversity + file_factor 

634 return min(total_score, 100.0) 

635 

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) 

644 

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', {}) 

655 

656 if prefs_dict.get('last_updated'): 

657 prefs.last_updated = datetime.fromisoformat(prefs_dict['last_updated']) 

658 

659 self.user_preferences[user_id] = prefs 

660 

661 logger.info(f"Loaded preferences for {len(self.user_preferences)} users") 

662 

663 except Exception as e: 

664 logger.error(f"Error loading analytics data: {e}") 

665 

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 } 

683 

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) 

687 

688 logger.info(f"Saved preferences for {len(self.user_preferences)} users") 

689 

690 except Exception as e: 

691 logger.error(f"Error saving analytics data: {e}") 

692 

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 } 

701 

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) 

707 

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 } 

720 

721 with open(output_path, 'w') as f: 

722 json.dump(export_data, f, indent=2) 

723 

724 return True 

725 

726 except Exception as e: 

727 logger.error(f"Error exporting data: {e}") 

728 return False 

729 

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() 

733 

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 } 

741 

742 today_start = current_time.replace(hour=0, minute=0, second=0, microsecond=0) 

743 

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 ] 

749 

750 metrics["total_sessions_today"] = len(today_sessions) 

751 

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) 

756 

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 }) 

767 

768 return metrics 

769 

770 

771# Global instance for easy access 

772_user_analytics: Optional[UserBehaviorAnalytics] = None 

773 

774 

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 

781 

782 

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 ) 

799 

800 

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) 

805 

806 

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) 

811 

812 

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) 

817 

818 

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) 

823 

824 

825if __name__ == "__main__": 

826 # Example usage 

827 print("Testing User Behavior Analytics...") 

828 

829 analytics = UserBehaviorAnalytics() 

830 

831 # Simulate user session 

832 user_id = "test_user" 

833 session_id = analytics.start_session(user_id, "/Users/goos/MoAI/MoAI-ADK", "main") 

834 

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 ) 

844 

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 ) 

853 

854 # End session 

855 ended_session = analytics.end_session(session_id) 

856 

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)}") 

863 

864 # Get patterns 

865 patterns = analytics.get_user_patterns(user_id, days=1) 

866 print(f"User patterns: {patterns}") 

867 

868 print("User Behavior Analytics test completed!")