$feature->getAnomalyScore(), $features ); $meanFeatureScore = array_sum($featureAnomalyScores) / count($featureAnomalyScores); $featureConsistency = 1.0 - (abs($anomalyScore - $meanFeatureScore) / max($anomalyScore, 0.01)); $baseConfidence *= $featureConsistency; } $confidence = Percentage::from(max(0.0, min(100.0, $baseConfidence))); return new self( type: $type, behaviorType: $behaviorType, confidence: $confidence, anomalyScore: $anomalyScore, description: $description, features: $features, evidence: $evidence, detectedAt: Timestamp::fromFloat(microtime(true)) ); } /** * Create frequency spike anomaly */ public static function frequencySpike( float $currentRate, float $baseline, float $threshold = 3.0, ?string $clientId = null ): self { $ratio = $baseline > 0 ? $currentRate / $baseline : $currentRate; $anomalyScore = min(($ratio - 1.0) / $threshold, 1.0); return self::create( type: AnomalyType::FREQUENCY_SPIKE, behaviorType: BehaviorType::REQUEST_FREQUENCY, anomalyScore: $anomalyScore, description: "Request frequency spike detected: {$currentRate}/s (baseline: {$baseline}/s, ratio: " . round($ratio, 2) . "x)", evidence: [ 'current_rate' => $currentRate, 'baseline_rate' => $baseline, 'spike_ratio' => $ratio, 'threshold' => $threshold, ] ); return $clientId !== null ? $anomaly->withClientId($clientId) : $anomaly; } /** * Create geographic anomaly */ public static function geographicAnomaly( string $currentLocation, array $normalLocations, float $distance, ?string $clientId = null ): self { $anomalyScore = min($distance / 10000, 1.0); // Normalize by 10,000 km return self::create( type: AnomalyType::GEOGRAPHIC_ANOMALY, behaviorType: BehaviorType::GEOGRAPHIC_PATTERNS, anomalyScore: $anomalyScore, description: "Geographic anomaly: access from {$currentLocation}, distance: " . round($distance) . "km from normal locations", evidence: [ 'current_location' => $currentLocation, 'normal_locations' => $normalLocations, 'distance_km' => $distance, ] ); return $clientId !== null ? $anomaly->withClientId($clientId) : $anomaly; } /** * Create pattern deviation anomaly */ public static function patternDeviation( BehaviorType $behaviorType, string $pattern, float $deviationScore, array $features = [] ): self { return self::create( type: AnomalyType::UNUSUAL_PATTERN, behaviorType: $behaviorType, anomalyScore: $deviationScore, description: "Unusual pattern detected in {$behaviorType->getDescription()}: {$pattern}", features: $features, evidence: [ 'pattern' => $pattern, 'deviation_score' => $deviationScore, 'feature_count' => count($features), ] ); } /** * Create statistical anomaly */ public static function statisticalAnomaly( BehaviorType $behaviorType, string $metric, float $value, float $expectedValue, float $standardDeviation, ?string $clientId = null ): self { $zScore = $standardDeviation > 0 ? abs($value - $expectedValue) / $standardDeviation : 0; $anomalyScore = min($zScore / 3.0, 1.0); // Normalize by 3 sigma return self::create( type: AnomalyType::STATISTICAL_ANOMALY, behaviorType: $behaviorType, anomalyScore: $anomalyScore, description: "Statistical anomaly in {$metric}: value={$value}, expected={$expectedValue}, z-score=" . round($zScore, 2), evidence: [ 'metric' => $metric, 'value' => $value, 'expected_value' => $expectedValue, 'standard_deviation' => $standardDeviation, 'z_score' => $zScore, ] ); return $clientId !== null ? $anomaly->withClientId($clientId) : $anomaly; } /** * Add client ID */ public function withClientId(string $clientId): self { return new self( type: $this->type, behaviorType: $this->behaviorType, confidence: $this->confidence, anomalyScore: $this->anomalyScore, description: $this->description, features: $this->features, evidence: $this->evidence, clientId: $clientId, sessionId: $this->sessionId, detectedAt: $this->detectedAt, analysisWindow: $this->analysisWindow, metadata: $this->metadata ); } /** * Add session ID */ public function withSessionId(string $sessionId): self { return new self( type: $this->type, behaviorType: $this->behaviorType, confidence: $this->confidence, anomalyScore: $this->anomalyScore, description: $this->description, features: $this->features, evidence: $this->evidence, clientId: $this->clientId, sessionId: $sessionId, detectedAt: $this->detectedAt, analysisWindow: $this->analysisWindow, metadata: $this->metadata ); } /** * Add analysis window */ public function withAnalysisWindow(Duration $window): self { return new self( type: $this->type, behaviorType: $this->behaviorType, confidence: $this->confidence, anomalyScore: $this->anomalyScore, description: $this->description, features: $this->features, evidence: $this->evidence, clientId: $this->clientId, sessionId: $this->sessionId, detectedAt: $this->detectedAt, analysisWindow: $window, metadata: $this->metadata ); } /** * Check if anomaly requires immediate action */ public function requiresImmediateAction(): bool { return $this->type->requiresImmediateAction() && $this->confidence->getValue() >= $this->type->getConfidenceThreshold() * 100; } /** * Get risk level */ public function getRiskLevel(): string { $confidenceScore = $this->confidence->getValue() / 100.0; $combinedScore = ($this->anomalyScore + $confidenceScore) / 2.0; return match (true) { $combinedScore >= 0.8 => 'critical', $combinedScore >= 0.6 => 'high', $combinedScore >= 0.4 => 'medium', $combinedScore >= 0.2 => 'low', default => 'info' }; } /** * Get recommended action */ public function getRecommendedAction(): string { return $this->type->getRecommendedAction(); } /** * Get severity score (0-100) */ public function getSeverityScore(): float { $typeWeight = match ($this->type->getSeverityLevel()) { 'high' => 0.9, 'medium' => 0.6, 'low' => 0.3, default => 0.5 }; $confidenceWeight = $this->confidence->getValue() / 100.0; $anomalyWeight = $this->anomalyScore; return ($typeWeight * 0.4 + $confidenceWeight * 0.3 + $anomalyWeight * 0.3) * 100; } /** * Convert to array for logging/storage */ public function toArray(): array { return [ 'type' => $this->type->value, 'behavior_type' => $this->behaviorType->value, 'confidence' => $this->confidence->getValue(), 'anomaly_score' => $this->anomalyScore, 'description' => $this->description, 'client_id' => $this->clientId, 'session_id' => $this->sessionId, 'detected_at' => $this->detectedAt?->format('c'), 'analysis_window_seconds' => $this->analysisWindow?->toSeconds(), 'features' => array_map(fn (BehaviorFeature $f) => $f->toArray(), $this->features), 'evidence' => $this->evidence, 'risk_level' => $this->getRiskLevel(), 'severity_score' => $this->getSeverityScore(), 'requires_immediate_action' => $this->requiresImmediateAction(), 'recommended_action' => $this->getRecommendedAction(), 'metadata' => $this->metadata, ]; } /** * Create summary for dashboard/alerting */ public function getSummary(): array { return [ 'id' => md5($this->type->value . $this->behaviorType->value . ($this->detectedAt?->format('c') ?? '')), 'type' => $this->type->value, 'description' => $this->description, 'risk_level' => $this->getRiskLevel(), 'confidence' => $this->confidence->getValue(), 'client_id' => $this->clientId, 'detected_at' => $this->detectedAt?->format('c'), 'requires_action' => $this->requiresImmediateAction(), ]; } }