SentinelAI / src /ai /behavior /profiling.py
ACA050's picture
Upload 114 files
5a3b9db verified
Raw
History Blame Contribute Delete
3.57 kB
"""
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