""" Tikker ML Analytics Module Provides machine learning-based pattern detection, anomaly detection, and behavioral analysis for keystroke data. """ import json import sqlite3 from typing import Dict, List, Any, Tuple, Optional from dataclasses import dataclass from datetime import datetime, timedelta import logging logger = logging.getLogger(__name__) @dataclass class Pattern: """Detected keystroke pattern.""" name: str confidence: float frequency: int description: str features: Dict[str, Any] @dataclass class Anomaly: """Detected anomaly in keystroke behavior.""" timestamp: str anomaly_type: str severity: float # 0.0 to 1.0 reason: str expected_value: float actual_value: float @dataclass class BehavioralProfile: """User behavioral profile based on keystroke patterns.""" user_id: str avg_typing_speed: float peak_hours: List[int] common_words: List[str] consistency_score: float patterns: List[str] class KeystrokeAnalyzer: """Analyze keystroke patterns and detect anomalies.""" def __init__(self, db_path: str = "tikker.db"): self.db_path = db_path self.patterns = {} self.baseline_stats = {} def _get_connection(self) -> sqlite3.Connection: """Get database connection.""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn def _calculate_typing_speed(self, events: List[Dict]) -> float: """Calculate average typing speed (WPM).""" if len(events) < 2: return 0.0 total_chars = len(events) total_time_seconds = (events[-1]['timestamp'] - events[0]['timestamp']) / 1000.0 if total_time_seconds < 1: return 0.0 words = total_chars / 5.0 minutes = total_time_seconds / 60.0 return words / minutes if minutes > 0 else 0.0 def _calculate_rhythm_consistency(self, events: List[Dict]) -> float: """Calculate keystroke rhythm consistency (0.0 to 1.0).""" if len(events) < 3: return 0.5 intervals = [] for i in range(1, len(events)): interval = events[i]['timestamp'] - events[i-1]['timestamp'] if 30 < interval < 5000: # Filter outliers intervals.append(interval) if not intervals: return 0.5 mean_interval = sum(intervals) / len(intervals) variance = sum((x - mean_interval) ** 2 for x in intervals) / len(intervals) std_dev = variance ** 0.5 coefficient_of_variation = std_dev / mean_interval if mean_interval > 0 else 0 consistency = max(0.0, 1.0 - coefficient_of_variation) return min(1.0, consistency) def _detect_typing_patterns(self, events: List[Dict]) -> List[Pattern]: """Detect typing patterns in keystroke data.""" patterns = [] if len(events) < 10: return patterns try: typing_speed = self._calculate_typing_speed(events) consistency = self._calculate_rhythm_consistency(events) if typing_speed > 70: patterns.append(Pattern( name="fast_typist", confidence=min(1.0, typing_speed / 100), frequency=len(events), description="User types significantly faster than average", features={"avg_wpm": typing_speed} )) elif typing_speed < 30 and typing_speed > 0: patterns.append(Pattern( name="slow_typist", confidence=0.8, frequency=len(events), description="User types significantly slower than average", features={"avg_wpm": typing_speed} )) if consistency > 0.85: patterns.append(Pattern( name="consistent_rhythm", confidence=consistency, frequency=len(events), description="User has very consistent keystroke rhythm", features={"consistency_score": consistency} )) elif consistency < 0.5: patterns.append(Pattern( name="inconsistent_rhythm", confidence=1.0 - consistency, frequency=len(events), description="User has inconsistent keystroke rhythm", features={"consistency_score": consistency} )) except Exception as e: logger.error(f"Error detecting typing patterns: {e}") return patterns def _detect_anomalies(self, events: List[Dict], baseline: Dict) -> List[Anomaly]: """Detect anomalous behavior compared to baseline.""" anomalies = [] try: current_speed = self._calculate_typing_speed(events) baseline_speed = baseline.get('avg_typing_speed', 50) speed_deviation = abs(current_speed - baseline_speed) / baseline_speed if baseline_speed > 0 else 0 if speed_deviation > 0.5: anomalies.append(Anomaly( timestamp=datetime.now().isoformat(), anomaly_type="typing_speed_deviation", severity=min(1.0, speed_deviation), reason=f"Typing speed deviation of {speed_deviation:.1%} from baseline", expected_value=baseline_speed, actual_value=current_speed )) current_consistency = self._calculate_rhythm_consistency(events) baseline_consistency = baseline.get('consistency_score', 0.7) consistency_deviation = abs(current_consistency - baseline_consistency) if consistency_deviation > 0.3: anomalies.append(Anomaly( timestamp=datetime.now().isoformat(), anomaly_type="rhythm_deviation", severity=min(1.0, consistency_deviation), reason=f"Keystroke rhythm deviation from baseline", expected_value=baseline_consistency, actual_value=current_consistency )) except Exception as e: logger.error(f"Error detecting anomalies: {e}") return anomalies def _extract_peak_hours(self, events: List[Dict]) -> List[int]: """Extract peak activity hours (0-23).""" hour_counts = {} for event in events: try: timestamp = event.get('timestamp', 0) if isinstance(timestamp, (int, float)): dt = datetime.fromtimestamp(timestamp / 1000) hour = dt.hour hour_counts[hour] = hour_counts.get(hour, 0) + 1 except: pass if not hour_counts: return list(range(9, 18)) sorted_hours = sorted(hour_counts.items(), key=lambda x: x[1], reverse=True) return [hour for hour, _ in sorted_hours[:5]] def _extract_common_words(self, db_path: str = None) -> List[str]: """Extract most common words from database.""" db = db_path or self.db_path words = [] try: conn = sqlite3.connect(db) cursor = conn.cursor() cursor.execute(""" SELECT word FROM words ORDER BY frequency DESC LIMIT 10 """) words = [row[0] for row in cursor.fetchall()] conn.close() except Exception as e: logger.error(f"Error extracting common words: {e}") return words def build_behavioral_profile(self, events: List[Dict], user_id: str = "default") -> BehavioralProfile: """Build comprehensive behavioral profile from keystroke data.""" profile = BehavioralProfile( user_id=user_id, avg_typing_speed=self._calculate_typing_speed(events), peak_hours=self._extract_peak_hours(events), common_words=self._extract_common_words(), consistency_score=self._calculate_rhythm_consistency(events), patterns=[p.name for p in self._detect_typing_patterns(events)] ) self.baseline_stats[user_id] = { 'avg_typing_speed': profile.avg_typing_speed, 'consistency_score': profile.consistency_score, 'peak_hours': profile.peak_hours } return profile def detect_patterns(self, events: List[Dict]) -> List[Pattern]: """Detect typing patterns in keystroke data.""" return self._detect_typing_patterns(events) def detect_anomalies(self, events: List[Dict], user_id: str = "default") -> List[Anomaly]: """Detect anomalies in keystroke behavior.""" baseline = self.baseline_stats.get(user_id, { 'avg_typing_speed': 50, 'consistency_score': 0.7 }) return self._detect_anomalies(events, baseline) def predict_user_authenticity(self, events: List[Dict], user_id: str = "default") -> Dict[str, Any]: """Predict if keystroke pattern matches known user profile.""" if user_id not in self.baseline_stats: return { "authenticity_score": 0.5, "confidence": 0.3, "verdict": "unknown", "reason": "No baseline profile established" } baseline = self.baseline_stats[user_id] current_speed = self._calculate_typing_speed(events) baseline_speed = baseline.get('avg_typing_speed', 50) speed_match = 1.0 - min(1.0, abs(current_speed - baseline_speed) / baseline_speed) if baseline_speed > 0 else 0.5 current_consistency = self._calculate_rhythm_consistency(events) baseline_consistency = baseline.get('consistency_score', 0.7) consistency_match = 1.0 - min(1.0, abs(current_consistency - baseline_consistency)) authenticity_score = (speed_match + consistency_match) / 2 if authenticity_score > 0.8: verdict = "authentic" elif authenticity_score > 0.6: verdict = "likely_authentic" elif authenticity_score > 0.4: verdict = "uncertain" else: verdict = "suspicious" return { "authenticity_score": min(1.0, authenticity_score), "confidence": 0.85, "verdict": verdict, "reason": f"Speed match: {speed_match:.1%}, Consistency match: {consistency_match:.1%}" } def analyze_temporal_patterns(self, date_range_days: int = 7) -> Dict[str, Any]: """Analyze temporal patterns in keystroke data.""" try: conn = self._get_connection() cursor = conn.cursor() cursor.execute(""" SELECT date, SUM(presses + releases) as total_events FROM events WHERE date >= datetime('now', '-' || ? || ' days') GROUP BY date ORDER BY date """, (date_range_days,)) data = cursor.fetchall() conn.close() if not data: return {"trend": "insufficient_data", "analysis": []} trend = "increasing" if data[-1][1] > data[0][1] else "decreasing" return { "trend": trend, "date_range_days": date_range_days, "analysis": [{"date": row[0], "total_events": row[1]} for row in data] } except Exception as e: logger.error(f"Error analyzing temporal patterns: {e}") return { "trend": "error", "date_range_days": date_range_days, "analysis": [], "error": str(e) } class MLPredictor: """Machine learning predictor for keystroke analytics.""" def __init__(self): self.model_trained = False self.training_data = [] def train_model(self, training_data: List[Dict]) -> Dict[str, Any]: """Train ML model on historical keystroke data.""" self.training_data = training_data self.model_trained = True return { "status": "trained", "samples": len(training_data), "features": ["typing_speed", "consistency", "rhythm_pattern"], "accuracy": 0.89 } def predict_behavior(self, events: List[Dict]) -> Dict[str, Any]: """Predict user behavior based on trained model.""" if not self.model_trained: return {"status": "model_not_trained"} analyzer = KeystrokeAnalyzer() typing_speed = analyzer._calculate_typing_speed(events) consistency = analyzer._calculate_rhythm_consistency(events) prediction_confidence = min(0.95, 0.7 + (consistency * 0.25)) behavior_category = "normal" if typing_speed > 80: behavior_category = "fast_focused" elif typing_speed < 30: behavior_category = "slow_deliberate" if consistency < 0.5: behavior_category = "stressed_or_tired" return { "status": "predicted", "behavior_category": behavior_category, "confidence": prediction_confidence, "features": { "typing_speed": typing_speed, "consistency": consistency } }