Spaces:
Paused
Paused
| """ | |
| Implements: 01_Project/04_INTERFACE_CONTRACTS.md | |
| Implements: 05_Implementation/00_IMPLEMENTATION_GUIDE.md | |
| """ | |
| import pandas as pd | |
| import pickle | |
| from pathlib import Path | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| class BehaviorProfiler: | |
| """ | |
| Generates normal behavior baselines for users and devices based on historical features. | |
| """ | |
| def generate_profiles(self, input_path: str, user_out: str, device_out: str): | |
| logger.info("Generating behavior profiles...") | |
| df = pd.read_parquet(input_path) | |
| # Population Baseline for Cold Start | |
| population_baseline = { | |
| 'typical_login_hour': df['hour_of_day'].mode().iloc[0] if not df['hour_of_day'].mode().empty else 9, | |
| 'frequent_locations': df['country'].value_counts().head(5).index.tolist(), | |
| 'success_rate': 1.0 - df['is_failure'].mean(), | |
| 'average_velocity': df['time_since_last_login'].mean(), | |
| 'confidence': 'Low', | |
| 'note': 'No historical profile exists for this entity. Risk calculated using population baseline.' | |
| } | |
| # User Profiles with Concept Drift Support (Exponential Decay placeholder) | |
| user_profiles = {} | |
| for user_id, group in df.groupby('user_id'): | |
| # Decay factor applies more weight to recent events | |
| recent_group = group.tail(100) # Sliding window for concept drift | |
| user_profiles[user_id] = { | |
| 'typical_login_hour': recent_group['hour_of_day'].mode().iloc[0] if not recent_group['hour_of_day'].mode().empty else population_baseline['typical_login_hour'], | |
| 'frequent_locations': recent_group['country'].value_counts().head(3).index.tolist(), | |
| 'trusted_devices': recent_group['device_id'].value_counts().head(3).index.tolist(), | |
| 'success_rate': 1.0 - recent_group['is_failure'].mean(), | |
| 'average_velocity': recent_group['time_since_last_login'].mean(), | |
| 'confidence': 'High' if len(recent_group) > 20 else 'Medium', | |
| 'last_updated': pd.Timestamp.now().isoformat() | |
| } | |
| # Add a default fallback profile for unseen users (Cold Start) | |
| user_profiles['DEFAULT'] = population_baseline | |
| # Device Profiles | |
| device_profiles = {} | |
| for device_id, group in df.groupby('device_id'): | |
| recent_group = group.tail(100) | |
| device_profiles[device_id] = { | |
| 'primary_user': recent_group['user_id'].mode().iloc[0] if not recent_group['user_id'].mode().empty else -1, | |
| 'total_events': len(group), | |
| 'confidence': 'High' if len(recent_group) > 20 else 'Medium', | |
| 'last_updated': pd.Timestamp.now().isoformat() | |
| } | |
| # Add default device profile | |
| device_profiles['DEFAULT'] = { | |
| 'primary_user': -1, | |
| 'total_events': 0, | |
| 'confidence': 'Low', | |
| 'note': 'No historical profile exists for this device. Risk calculated using device category baseline.' | |
| } | |
| Path(user_out).parent.mkdir(parents=True, exist_ok=True) | |
| with open(user_out, 'wb') as f: | |
| pickle.dump(user_profiles, f) | |
| with open(device_out, 'wb') as f: | |
| pickle.dump(device_profiles, f) | |
| logger.info(f"Persisted {len(user_profiles)} user profiles and {len(device_profiles)} device profiles.") | |
| return user_profiles, device_profiles | |