feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,364 @@
<?php
declare(strict_types=1);
namespace App\Framework\MachineLearning\ValueObjects;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Represents a detected anomaly from machine learning analysis
*
* This value object is domain-agnostic and can represent anomalies detected
* across any framework component:
* - WAF: Malicious request patterns, attack signatures
* - Query Optimization: N+1 query patterns, performance anomalies
* - Performance Monitoring: Response time spikes, resource exhaustion
* - Security Events: Authentication failures, privilege escalation
*
* Design Principles:
* - Immutable value object (readonly)
* - Rich domain modeling with confidence, risk, and severity scoring
* - Factory methods for common anomaly types
* - Self-contained evidence and metadata
* - Framework-agnostic (uses generic FeatureType instead of domain-specific types)
*/
final readonly class AnomalyDetection
{
/**
* @param AnomalyType $type Type of anomaly detected
* @param FeatureType $featureType Feature category this anomaly relates to
* @param Percentage $confidence Detection confidence (0-100%)
* @param float $anomalyScore Normalized anomaly score (0.0-1.0)
* @param string $description Human-readable anomaly description
* @param array<Feature> $features Features that contributed to detection
* @param array<string, mixed> $evidence Supporting evidence for the detection
* @param string|null $entityId Optional entity identifier (user, IP, session, etc.)
* @param string|null $contextId Optional context identifier (request, query, transaction, etc.)
* @param Timestamp|null $detectedAt When anomaly was detected
* @param Duration|null $analysisWindow Time window used for analysis
* @param array<string, mixed> $metadata Additional anomaly metadata
*/
public function __construct(
public AnomalyType $type,
public FeatureType $featureType,
public Percentage $confidence,
public float $anomalyScore,
public string $description,
public array $features,
public array $evidence,
public ?string $entityId = null,
public ?string $contextId = null,
public ?Timestamp $detectedAt = null,
public ?Duration $analysisWindow = null,
public array $metadata = []
) {}
/**
* Create anomaly detection with automatic confidence calculation
*/
public static function create(
AnomalyType $type,
FeatureType $featureType,
float $anomalyScore,
string $description,
array $features = [],
array $evidence = []
): self {
// Calculate confidence based on anomaly score and feature consistency
$baseConfidence = min($anomalyScore * 100, 100.0);
// Adjust confidence based on feature agreement
if (!empty($features)) {
$featureAnomalyScores = array_map(
fn(Feature $feature) => $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,
featureType: $featureType,
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 $entityId = null
): self {
$ratio = $baseline > 0 ? $currentRate / $baseline : $currentRate;
$anomalyScore = min(($ratio - 1.0) / $threshold, 1.0);
$anomaly = self::create(
type: AnomalyType::FREQUENCY_SPIKE,
featureType: FeatureType::FREQUENCY,
anomalyScore: $anomalyScore,
description: "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 $entityId !== null ? $anomaly->withEntityId($entityId) : $anomaly;
}
/**
* Create geographic anomaly
*/
public static function geographicAnomaly(
string $currentLocation,
array $normalLocations,
float $distance,
?string $entityId = null
): self {
$anomalyScore = min($distance / 10000, 1.0); // Normalize by 10,000 km
$anomaly = self::create(
type: AnomalyType::GEOGRAPHIC_ANOMALY,
featureType: FeatureType::GEOGRAPHIC_DISTRIBUTION,
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 $entityId !== null ? $anomaly->withEntityId($entityId) : $anomaly;
}
/**
* Create pattern deviation anomaly
*/
public static function patternDeviation(
FeatureType $featureType,
string $pattern,
float $deviationScore,
array $features = []
): self {
return self::create(
type: AnomalyType::UNUSUAL_PATTERN,
featureType: $featureType,
anomalyScore: $deviationScore,
description: "Unusual pattern detected in {$featureType->getDescription()}: {$pattern}",
features: $features,
evidence: [
'pattern' => $pattern,
'deviation_score' => $deviationScore,
'feature_count' => count($features),
]
);
}
/**
* Create statistical anomaly
*/
public static function statisticalAnomaly(
FeatureType $featureType,
string $metric,
float $value,
float $expectedValue,
float $standardDeviation,
?string $entityId = null
): self {
$zScore = $standardDeviation > 0 ? abs($value - $expectedValue) / $standardDeviation : 0;
$anomalyScore = min($zScore / 3.0, 1.0); // Normalize by 3 sigma
$anomaly = self::create(
type: AnomalyType::STATISTICAL_ANOMALY,
featureType: $featureType,
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 $entityId !== null ? $anomaly->withEntityId($entityId) : $anomaly;
}
/**
* Add entity ID (user, IP, session, etc.)
*/
public function withEntityId(string $entityId): self
{
return new self(
type: $this->type,
featureType: $this->featureType,
confidence: $this->confidence,
anomalyScore: $this->anomalyScore,
description: $this->description,
features: $this->features,
evidence: $this->evidence,
entityId: $entityId,
contextId: $this->contextId,
detectedAt: $this->detectedAt,
analysisWindow: $this->analysisWindow,
metadata: $this->metadata
);
}
/**
* Add context ID (request, query, transaction, etc.)
*/
public function withContextId(string $contextId): self
{
return new self(
type: $this->type,
featureType: $this->featureType,
confidence: $this->confidence,
anomalyScore: $this->anomalyScore,
description: $this->description,
features: $this->features,
evidence: $this->evidence,
entityId: $this->entityId,
contextId: $contextId,
detectedAt: $this->detectedAt,
analysisWindow: $this->analysisWindow,
metadata: $this->metadata
);
}
/**
* Add analysis window
*/
public function withAnalysisWindow(Duration $window): self
{
return new self(
type: $this->type,
featureType: $this->featureType,
confidence: $this->confidence,
anomalyScore: $this->anomalyScore,
description: $this->description,
features: $this->features,
evidence: $this->evidence,
entityId: $this->entityId,
contextId: $this->contextId,
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,
'feature_type' => $this->featureType->value,
'confidence' => $this->confidence->getValue(),
'anomaly_score' => $this->anomalyScore,
'description' => $this->description,
'entity_id' => $this->entityId,
'context_id' => $this->contextId,
'detected_at' => $this->detectedAt?->format('c'),
'analysis_window_seconds' => $this->analysisWindow?->toSeconds(),
'features' => array_map(fn(Feature $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->featureType->value . ($this->detectedAt?->format('c') ?? '')),
'type' => $this->type->value,
'description' => $this->description,
'risk_level' => $this->getRiskLevel(),
'confidence' => $this->confidence->getValue(),
'entity_id' => $this->entityId,
'detected_at' => $this->detectedAt?->format('c'),
'requires_action' => $this->requiresImmediateAction(),
];
}
}

View File

@@ -0,0 +1,301 @@
<?php
declare(strict_types=1);
namespace App\Framework\MachineLearning\ValueObjects;
/**
* Types of anomalies detected by ML analysis
*
* This enum is domain-agnostic and categorizes anomalies by their
* detection method and characteristics rather than domain-specific behavior
*/
enum AnomalyType: string
{
// Frequency-based anomalies
case FREQUENCY_SPIKE = 'frequency_spike';
case RATE_CHANGE = 'rate_change';
case VOLUME_ANOMALY = 'volume_anomaly';
// Pattern-based anomalies
case UNUSUAL_PATTERN = 'unusual_pattern';
case SEQUENCE_ANOMALY = 'sequence_anomaly';
case BEHAVIORAL_DRIFT = 'behavioral_drift';
// Statistical anomalies
case STATISTICAL_ANOMALY = 'statistical_anomaly';
case OUTLIER_DETECTION = 'outlier_detection';
case DISTRIBUTION_SHIFT = 'distribution_shift';
// Temporal anomalies
case TEMPORAL_ANOMALY = 'temporal_anomaly';
case SEASONAL_DEVIATION = 'seasonal_deviation';
case TREND_BREAK = 'trend_break';
// Correlation anomalies
case CORRELATION_BREAK = 'correlation_break';
case DEPENDENCY_VIOLATION = 'dependency_violation';
// Clustering anomalies
case CLUSTERING_DEVIATION = 'clustering_deviation';
case CLUSTER_DEVIATION = 'cluster_deviation';
case DENSITY_ANOMALY = 'density_anomaly';
// Geographic anomalies
case GEOGRAPHIC_ANOMALY = 'geographic_anomaly';
case LOCATION_DEVIATION = 'location_deviation';
// Group anomalies
case GROUP_ANOMALY = 'group_anomaly';
case COLLECTIVE_ANOMALY = 'collective_anomaly';
/**
* Get human-readable description
*/
public function getDescription(): string
{
return match ($this) {
self::FREQUENCY_SPIKE => 'Unusual spike in event frequency',
self::RATE_CHANGE => 'Significant change in occurrence rate',
self::VOLUME_ANOMALY => 'Abnormal volume or quantity',
self::UNUSUAL_PATTERN => 'Deviation from established patterns',
self::SEQUENCE_ANOMALY => 'Unusual sequence or order of events',
self::BEHAVIORAL_DRIFT => 'Gradual shift in baseline behavior',
self::STATISTICAL_ANOMALY => 'Statistical significance in change',
self::OUTLIER_DETECTION => 'Statistical outlier in metrics',
self::DISTRIBUTION_SHIFT => 'Change in value distribution',
self::TEMPORAL_ANOMALY => 'Abnormal timing or temporal patterns',
self::SEASONAL_DEVIATION => 'Deviation from seasonal patterns',
self::TREND_BREAK => 'Break in established trends',
self::CORRELATION_BREAK => 'Break in correlation patterns',
self::DEPENDENCY_VIOLATION => 'Violation of dependency relationships',
self::CLUSTERING_DEVIATION => 'Deviation from clustering patterns',
self::CLUSTER_DEVIATION => 'Deviation from cluster assignment',
self::DENSITY_ANOMALY => 'Abnormal density in feature space',
self::GEOGRAPHIC_ANOMALY => 'Unusual geographic patterns',
self::LOCATION_DEVIATION => 'Unexpected location or distribution',
self::GROUP_ANOMALY => 'Anomalous group behavior',
self::COLLECTIVE_ANOMALY => 'Collective deviation from norms',
};
}
/**
* Get default confidence threshold for this anomaly type
*/
public function getConfidenceThreshold(): float
{
return match ($this) {
// High confidence required (90%+)
self::STATISTICAL_ANOMALY => 0.90,
// Medium-high confidence (85%+)
self::FREQUENCY_SPIKE,
self::OUTLIER_DETECTION,
self::DENSITY_ANOMALY => 0.85,
// Medium confidence (80%+)
self::GEOGRAPHIC_ANOMALY,
self::SEQUENCE_ANOMALY,
self::CLUSTER_DEVIATION,
self::RATE_CHANGE => 0.80,
// Medium-low confidence (75%+)
self::UNUSUAL_PATTERN,
self::CLUSTERING_DEVIATION,
self::CORRELATION_BREAK,
self::GROUP_ANOMALY,
self::VOLUME_ANOMALY => 0.75,
// Lower confidence (70%+)
self::TEMPORAL_ANOMALY,
self::LOCATION_DEVIATION,
self::TREND_BREAK => 0.70,
// Low confidence (60%+)
self::BEHAVIORAL_DRIFT,
self::SEASONAL_DEVIATION,
self::DISTRIBUTION_SHIFT,
self::DEPENDENCY_VIOLATION,
self::COLLECTIVE_ANOMALY => 0.60,
};
}
/**
* Get severity level for this anomaly type
*/
public function getSeverityLevel(): string
{
return match ($this) {
// High severity
self::FREQUENCY_SPIKE,
self::STATISTICAL_ANOMALY,
self::DENSITY_ANOMALY => 'high',
// Medium severity
self::UNUSUAL_PATTERN,
self::GEOGRAPHIC_ANOMALY,
self::OUTLIER_DETECTION,
self::CLUSTERING_DEVIATION,
self::SEQUENCE_ANOMALY,
self::CORRELATION_BREAK,
self::CLUSTER_DEVIATION,
self::GROUP_ANOMALY,
self::RATE_CHANGE,
self::VOLUME_ANOMALY => 'medium',
// Low severity
self::TEMPORAL_ANOMALY,
self::BEHAVIORAL_DRIFT,
self::LOCATION_DEVIATION,
self::SEASONAL_DEVIATION,
self::TREND_BREAK,
self::DISTRIBUTION_SHIFT,
self::DEPENDENCY_VIOLATION,
self::COLLECTIVE_ANOMALY => 'low',
};
}
/**
* Check if this anomaly requires immediate action
*/
public function requiresImmediateAction(): bool
{
return match ($this) {
self::FREQUENCY_SPIKE,
self::STATISTICAL_ANOMALY,
self::DENSITY_ANOMALY => true,
default => false
};
}
/**
* Get recommended action for this anomaly type
*/
public function getRecommendedAction(): string
{
return match ($this) {
self::FREQUENCY_SPIKE => 'Rate limiting, throttling, or temporary blocking',
self::RATE_CHANGE => 'Investigate rate change cause, adjust baselines',
self::VOLUME_ANOMALY => 'Capacity analysis, volume threshold adjustment',
self::UNUSUAL_PATTERN => 'Enhanced monitoring, pattern analysis',
self::SEQUENCE_ANOMALY => 'Sequence analysis, flow monitoring',
self::BEHAVIORAL_DRIFT => 'Baseline update, long-term monitoring',
self::STATISTICAL_ANOMALY => 'Immediate investigation, possible intervention',
self::OUTLIER_DETECTION => 'Detailed analysis, manual review',
self::DISTRIBUTION_SHIFT => 'Distribution analysis, model retraining',
self::TEMPORAL_ANOMALY => 'Schedule analysis, time-based rules',
self::SEASONAL_DEVIATION => 'Seasonal pattern review, calendar analysis',
self::TREND_BREAK => 'Trend analysis, change point detection',
self::CORRELATION_BREAK => 'Correlation analysis, relationship mapping',
self::DEPENDENCY_VIOLATION => 'Dependency graph review, constraint validation',
self::CLUSTERING_DEVIATION => 'Cluster analysis, behavior profiling',
self::CLUSTER_DEVIATION => 'Cluster reanalysis, pattern adjustment',
self::DENSITY_ANOMALY => 'Density analysis, space examination',
self::GEOGRAPHIC_ANOMALY => 'Geographic verification, location analysis',
self::LOCATION_DEVIATION => 'Location tracking, geofence review',
self::GROUP_ANOMALY => 'Group analysis, collective behavior review',
self::COLLECTIVE_ANOMALY => 'Aggregate analysis, population study',
};
}
/**
* Get analysis complexity for this anomaly type
*/
public function getAnalysisComplexity(): string
{
return match ($this) {
// Low complexity
self::FREQUENCY_SPIKE,
self::RATE_CHANGE,
self::VOLUME_ANOMALY => 'low',
// Medium complexity
self::STATISTICAL_ANOMALY,
self::OUTLIER_DETECTION,
self::TEMPORAL_ANOMALY,
self::GEOGRAPHIC_ANOMALY,
self::GROUP_ANOMALY,
self::LOCATION_DEVIATION => 'medium',
// High complexity
self::UNUSUAL_PATTERN,
self::BEHAVIORAL_DRIFT,
self::CLUSTERING_DEVIATION,
self::SEQUENCE_ANOMALY,
self::CORRELATION_BREAK,
self::CLUSTER_DEVIATION,
self::DENSITY_ANOMALY,
self::SEASONAL_DEVIATION,
self::TREND_BREAK,
self::DISTRIBUTION_SHIFT,
self::DEPENDENCY_VIOLATION,
self::COLLECTIVE_ANOMALY => 'high',
};
}
/**
* Check if this anomaly type is frequency-based
*/
public function isFrequencyBased(): bool
{
return match ($this) {
self::FREQUENCY_SPIKE,
self::RATE_CHANGE,
self::VOLUME_ANOMALY => true,
default => false
};
}
/**
* Check if this anomaly type is pattern-based
*/
public function isPatternBased(): bool
{
return match ($this) {
self::UNUSUAL_PATTERN,
self::SEQUENCE_ANOMALY,
self::BEHAVIORAL_DRIFT => true,
default => false
};
}
/**
* Check if this anomaly type is statistical
*/
public function isStatistical(): bool
{
return match ($this) {
self::STATISTICAL_ANOMALY,
self::OUTLIER_DETECTION,
self::DISTRIBUTION_SHIFT => true,
default => false
};
}
/**
* Check if this anomaly type is temporal
*/
public function isTemporal(): bool
{
return match ($this) {
self::TEMPORAL_ANOMALY,
self::SEASONAL_DEVIATION,
self::TREND_BREAK => true,
default => false
};
}
}

View File

@@ -0,0 +1,451 @@
<?php
declare(strict_types=1);
namespace App\Framework\MachineLearning\ValueObjects;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Represents a statistical baseline for anomaly detection
*
* A baseline captures the "normal" behavior of a system by tracking
* statistical properties of feature values over time. This enables
* detection of deviations from expected patterns.
*
* Statistical Properties:
* - Central Tendency: mean, median
* - Spread: standard deviation, variance, range
* - Shape: skewness, kurtosis
* - Percentiles: P25, P50, P75, P90, P95, P99
* - Confidence: based on sample size and consistency
*
* Use Cases:
* - WAF: Baseline request patterns, error rates, response times
* - Query: Baseline query execution times, pattern frequencies
* - Performance: Baseline latency, memory usage, cache hit rates
* - Security: Baseline login attempts, access patterns
*/
final readonly class Baseline
{
/**
* @param FeatureType $type Feature type this baseline represents
* @param string $identifier Identifier for baseline scope (e.g., "global", "user:123", "ip:1.2.3.4")
* @param float $mean Average value
* @param float $standardDeviation Standard deviation from mean
* @param float $median Median value (50th percentile)
* @param float $minimum Minimum observed value
* @param float $maximum Maximum observed value
* @param array<int, float> $percentiles Percentile values (25, 75, 90, 95, 99)
* @param int $sampleCount Number of samples used to build baseline
* @param Timestamp $createdAt When baseline was initially created
* @param Timestamp $lastUpdated When baseline was last updated
* @param Duration $windowSize Time window for baseline calculation
* @param float $confidence Confidence score (0-1) based on sample size and consistency
* @param array<string, mixed> $metadata Additional baseline metadata
*/
public function __construct(
public FeatureType $type,
public string $identifier,
public float $mean,
public float $standardDeviation,
public float $median,
public float $minimum,
public float $maximum,
public array $percentiles,
public int $sampleCount,
public Timestamp $createdAt,
public Timestamp $lastUpdated,
public Duration $windowSize,
public float $confidence,
public array $metadata = []
) {}
/**
* Create baseline from statistical data
*
* @param float[] $values Sample values
*/
public static function fromStatistics(
FeatureType $type,
string $identifier,
array $values,
Duration $windowSize
): self {
if (empty($values)) {
throw new \InvalidArgumentException('Cannot create baseline from empty values');
}
sort($values);
$count = count($values);
$mean = array_sum($values) / $count;
$variance = self::calculateVariance($values, $mean);
$standardDeviation = sqrt($variance);
$median = self::calculatePercentile($values, 50);
$percentiles = [
25 => self::calculatePercentile($values, 25),
75 => self::calculatePercentile($values, 75),
90 => self::calculatePercentile($values, 90),
95 => self::calculatePercentile($values, 95),
99 => self::calculatePercentile($values, 99),
];
// Calculate confidence based on sample size and data consistency
$confidence = self::calculateConfidence($count, $standardDeviation, $mean);
$now = Timestamp::now();
return new self(
type: $type,
identifier: $identifier,
mean: $mean,
standardDeviation: $standardDeviation,
median: $median,
minimum: min($values),
maximum: max($values),
percentiles: $percentiles,
sampleCount: $count,
createdAt: $now,
lastUpdated: $now,
windowSize: $windowSize,
confidence: $confidence,
metadata: [
'variance' => $variance,
'range' => max($values) - min($values),
'coefficient_of_variation' => $mean > 0 ? $standardDeviation / $mean : 0,
'skewness' => self::calculateSkewness($values, $mean, $standardDeviation),
'kurtosis' => self::calculateKurtosis($values, $mean, $standardDeviation),
]
);
}
/**
* Update baseline with new values (exponential moving average)
*
* @param float[] $newValues
*/
public function updateWith(array $newValues, float $learningRate = 0.1): self
{
if (empty($newValues)) {
return $this;
}
$newMean = array_sum($newValues) / count($newValues);
$newVariance = self::calculateVariance($newValues, $newMean);
$newStdDev = sqrt($newVariance);
// Exponential moving average update
$updatedMean = $this->mean * (1 - $learningRate) + $newMean * $learningRate;
$updatedStdDev = $this->standardDeviation * (1 - $learningRate) + $newStdDev * $learningRate;
// Update other statistics with weighted average
sort($newValues);
$newMedian = self::calculatePercentile($newValues, 50);
$updatedMedian = $this->median * (1 - $learningRate) + $newMedian * $learningRate;
$newMin = min($newValues);
$newMax = max($newValues);
$updatedMin = min($this->minimum, $newMin);
$updatedMax = max($this->maximum, $newMax);
// Update percentiles
$updatedPercentiles = [];
foreach ([25, 75, 90, 95, 99] as $percentile) {
$newPercentileValue = self::calculatePercentile($newValues, $percentile);
$updatedPercentiles[$percentile] = $this->percentiles[$percentile] * (1 - $learningRate) +
$newPercentileValue * $learningRate;
}
$newSampleCount = $this->sampleCount + count($newValues);
$updatedConfidence = self::calculateConfidence($newSampleCount, $updatedStdDev, $updatedMean);
return new self(
type: $this->type,
identifier: $this->identifier,
mean: $updatedMean,
standardDeviation: $updatedStdDev,
median: $updatedMedian,
minimum: $updatedMin,
maximum: $updatedMax,
percentiles: $updatedPercentiles,
sampleCount: $newSampleCount,
createdAt: $this->createdAt,
lastUpdated: Timestamp::now(),
windowSize: $this->windowSize,
confidence: $updatedConfidence,
metadata: array_merge($this->metadata, [
'last_update_sample_count' => count($newValues),
'learning_rate' => $learningRate,
'update_timestamp' => Timestamp::now()->toIsoString(),
])
);
}
/**
* Calculate Z-score for a given value
*/
public function calculateZScore(float $value): float
{
if ($this->standardDeviation <= 0) {
return 0.0;
}
return ($value - $this->mean) / $this->standardDeviation;
}
/**
* Check if value is anomalous based on Z-score threshold
*/
public function isAnomalous(float $value, float $threshold = 2.0): bool
{
return abs($this->calculateZScore($value)) > $threshold;
}
/**
* Get anomaly score for a value (0-1, where 1 is most anomalous)
*/
public function getAnomalyScore(float $value): float
{
$zScore = abs($this->calculateZScore($value));
// Use sigmoid function to convert Z-score to 0-1 range
// Threshold at 2 standard deviations
return 1 / (1 + exp(-($zScore - 2)));
}
/**
* Get percentile rank for a value
*/
public function getPercentileRank(float $value): float
{
if ($value <= $this->minimum) {
return 0.0;
}
if ($value >= $this->maximum) {
return 100.0;
}
// Approximate percentile rank using normal distribution
$zScore = $this->calculateZScore($value);
// Using standard normal CDF approximation
$cdf = 0.5 * (1 + self::erf($zScore / sqrt(2)));
return $cdf * 100;
}
/**
* Check if baseline is reliable for detection
*/
public function isReliable(): bool
{
$minSamples = $this->type->getMinSampleSize();
$minConfidence = 0.7;
return $this->sampleCount >= $minSamples &&
$this->confidence >= $minConfidence &&
$this->standardDeviation > 0;
}
/**
* Get age of baseline
*/
public function getAge(): Duration
{
return $this->createdAt->diff(Timestamp::now());
}
/**
* Check if baseline needs refresh
*/
public function needsRefresh(Duration $maxAge): bool
{
return $this->getAge()->isGreaterThan($maxAge) ||
$this->confidence < 0.5;
}
/**
* Create feature for anomaly detection
*/
public function createFeature(string $name, float $value): Feature
{
return Feature::create(
type: $this->type,
name: $name,
value: $value,
baseline: $this->mean,
standardDeviation: $this->standardDeviation
);
}
/**
* Get summary statistics
*
* @return array<string, mixed>
*/
public function getSummary(): array
{
return [
'type' => $this->type->value,
'identifier' => $this->identifier,
'mean' => round($this->mean, 4),
'std_dev' => round($this->standardDeviation, 4),
'median' => round($this->median, 4),
'min' => round($this->minimum, 4),
'max' => round($this->maximum, 4),
'sample_count' => $this->sampleCount,
'confidence' => round($this->confidence, 3),
'age_hours' => round($this->getAge()->toHours(), 1),
'is_reliable' => $this->isReliable(),
];
}
/**
* Convert to array for storage
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'type' => $this->type->value,
'identifier' => $this->identifier,
'mean' => $this->mean,
'standard_deviation' => $this->standardDeviation,
'median' => $this->median,
'minimum' => $this->minimum,
'maximum' => $this->maximum,
'percentiles' => $this->percentiles,
'sample_count' => $this->sampleCount,
'created_at' => $this->createdAt->toIsoString(),
'last_updated' => $this->lastUpdated->toIsoString(),
'window_size_seconds' => $this->windowSize->toSeconds(),
'confidence' => $this->confidence,
'metadata' => $this->metadata,
];
}
/**
* Calculate variance
*
* @param float[] $values
*/
private static function calculateVariance(array $values, float $mean): float
{
if (count($values) < 2) {
return 0.0;
}
$sumSquaredDifferences = array_sum(array_map(
fn($value) => pow($value - $mean, 2),
$values
));
return $sumSquaredDifferences / (count($values) - 1);
}
/**
* Calculate percentile
*
* @param float[] $sortedValues Must be pre-sorted
*/
private static function calculatePercentile(array $sortedValues, float $percentile): float
{
$count = count($sortedValues);
$index = ($percentile / 100) * ($count - 1);
if ($index == floor($index)) {
return $sortedValues[(int) $index];
}
$lower = $sortedValues[(int) floor($index)];
$upper = $sortedValues[(int) ceil($index)];
$fraction = $index - floor($index);
return $lower + ($upper - $lower) * $fraction;
}
/**
* Calculate confidence based on sample size and consistency
*/
private static function calculateConfidence(int $sampleCount, float $stdDev, float $mean): float
{
// Base confidence on sample size (asymptotic to 1)
$sizeConfidence = 1 - exp(-$sampleCount / 50);
// Penalize high variability
$coefficientOfVariation = $mean > 0 ? $stdDev / $mean : 1;
$consistencyConfidence = 1 / (1 + $coefficientOfVariation);
return min(1.0, $sizeConfidence * $consistencyConfidence);
}
/**
* Calculate skewness
*
* @param float[] $values
*/
private static function calculateSkewness(array $values, float $mean, float $stdDev): float
{
if ($stdDev <= 0 || count($values) < 3) {
return 0.0;
}
$n = count($values);
$sum = array_sum(array_map(
fn($value) => pow(($value - $mean) / $stdDev, 3),
$values
));
return ($n / (($n - 1) * ($n - 2))) * $sum;
}
/**
* Calculate kurtosis
*
* @param float[] $values
*/
private static function calculateKurtosis(array $values, float $mean, float $stdDev): float
{
if ($stdDev <= 0 || count($values) < 4) {
return 0.0;
}
$n = count($values);
$sum = array_sum(array_map(
fn($value) => pow(($value - $mean) / $stdDev, 4),
$values
));
$kurtosis = (($n * ($n + 1)) / (($n - 1) * ($n - 2) * ($n - 3))) * $sum;
$correction = (3 * pow($n - 1, 2)) / (($n - 2) * ($n - 3));
return $kurtosis - $correction;
}
/**
* Error function approximation for normal distribution CDF
*/
private static function erf(float $x): float
{
// Abramowitz and Stegun approximation
$a1 = 0.254829592;
$a2 = -0.284496736;
$a3 = 1.421413741;
$a4 = -1.453152027;
$a5 = 1.061405429;
$p = 0.3275911;
$sign = $x < 0 ? -1 : 1;
$x = abs($x);
$t = 1.0 / (1.0 + $p * $x);
$y = 1.0 - ((((($a5 * $t + $a4) * $t) + $a3) * $t + $a2) * $t + $a1) * $t * exp(-$x * $x);
return $sign * $y;
}
}

View File

@@ -0,0 +1,329 @@
<?php
declare(strict_types=1);
namespace App\Framework\MachineLearning\ValueObjects;
/**
* Represents a single feature extracted from domain-specific data
*
* Features are the foundation of machine learning analysis. This value object
* encapsulates a feature with its statistical properties for anomaly detection.
*
* Domain Examples:
* - WAF: Request frequency, pattern deviation, geographic anomalies
* - Query: Execution frequency, pattern complexity, caller consistency
* - Performance: Response time, memory usage, cache efficiency
* - Security: Login attempts, error rates, access patterns
*/
final readonly class Feature
{
/**
* @param FeatureType $type Feature type category
* @param string $name Feature identifier (e.g., "request_frequency", "query_count")
* @param float $value Current measured value
* @param string $unit Unit of measurement (e.g., "count", "ms", "bytes", "percentage")
* @param float|null $baseline Expected/normal value (if available)
* @param float|null $standardDeviation Standard deviation from baseline (if available)
* @param float|null $zScore Z-score (standard deviations from baseline)
* @param float|null $normalizedValue Normalized value (0-1 range)
* @param array<string, mixed> $metadata Additional feature metadata
*/
public function __construct(
public FeatureType $type,
public string $name,
public float $value,
public string $unit = 'count',
public ?float $baseline = null,
public ?float $standardDeviation = null,
public ?float $zScore = null,
public ?float $normalizedValue = null,
public array $metadata = []
) {}
/**
* Create feature for frequency-based measurements
*/
public static function frequency(
FeatureType $type,
string $name,
int $count,
?float $baseline = null,
?float $standardDeviation = null
): self {
$value = (float) $count;
$zScore = null;
$normalizedValue = null;
if ($baseline !== null && $standardDeviation !== null && $standardDeviation > 0) {
$zScore = ($value - $baseline) / $standardDeviation;
// Normalize using sigmoid function
$normalizedValue = 1 / (1 + exp(-$zScore));
}
return new self(
type: $type,
name: $name,
value: $value,
unit: 'count',
baseline: $baseline,
standardDeviation: $standardDeviation,
zScore: $zScore,
normalizedValue: $normalizedValue
);
}
/**
* Create feature for ratio/percentage measurements
*/
public static function ratio(
FeatureType $type,
string $name,
float $ratio,
?float $baseline = null,
?float $standardDeviation = null
): self {
$zScore = null;
$normalizedValue = $ratio; // Ratios are already 0-1
if ($baseline !== null && $standardDeviation !== null && $standardDeviation > 0) {
$zScore = ($ratio - $baseline) / $standardDeviation;
}
return new self(
type: $type,
name: $name,
value: $ratio,
unit: 'ratio',
baseline: $baseline,
standardDeviation: $standardDeviation,
zScore: $zScore,
normalizedValue: $normalizedValue
);
}
/**
* Create feature for entropy/diversity measurements
*/
public static function entropy(
FeatureType $type,
string $name,
float $entropy,
float $maxEntropy,
?float $baseline = null
): self {
$normalizedValue = $maxEntropy > 0 ? $entropy / $maxEntropy : 0.0;
return new self(
type: $type,
name: $name,
value: $entropy,
unit: 'bits',
baseline: $baseline,
normalizedValue: $normalizedValue,
metadata: ['max_entropy' => $maxEntropy]
);
}
/**
* Create feature with statistical properties
*/
public static function create(
FeatureType $type,
string $name,
float $value,
string $unit = 'count',
?float $baseline = null,
?float $standardDeviation = null
): self {
$zScore = null;
$normalizedValue = null;
// Calculate Z-score if baseline and std dev are provided
if ($baseline !== null && $standardDeviation !== null && $standardDeviation > 0) {
$zScore = ($value - $baseline) / $standardDeviation;
// Normalize to 0-1 range using sigmoid function
$normalizedValue = 1 / (1 + exp(-$zScore));
}
return new self(
type: $type,
name: $name,
value: $value,
unit: $unit,
baseline: $baseline,
standardDeviation: $standardDeviation,
zScore: $zScore,
normalizedValue: $normalizedValue
);
}
/**
* Calculate anomaly score (0-1, where 1 is most anomalous)
*/
public function getAnomalyScore(): float
{
if ($this->zScore === null) {
return 0.0;
}
// Convert Z-score to anomaly score using sigmoid function
// Threshold at 2 standard deviations
$absZScore = abs($this->zScore);
return 1 / (1 + exp(-($absZScore - 2)));
}
/**
* Check if feature is anomalous based on Z-score threshold
*/
public function isAnomalous(float $zScoreThreshold = 2.0): bool
{
return $this->zScore !== null && abs($this->zScore) > $zScoreThreshold;
}
/**
* Get deviation from baseline as percentage
*/
public function getDeviationPercentage(): ?float
{
if ($this->baseline === null || $this->baseline == 0) {
return null;
}
return (($this->value - $this->baseline) / $this->baseline) * 100;
}
/**
* Combine multiple features using weighted average
*
* @param Feature[] $features
* @param float[] $weights
*/
public static function combine(array $features, array $weights = []): self
{
if (empty($features)) {
throw new \InvalidArgumentException('Cannot combine empty feature array');
}
// Use equal weights if not provided
if (empty($weights)) {
$weights = array_fill(0, count($features), 1.0 / count($features));
}
if (count($features) !== count($weights)) {
throw new \InvalidArgumentException('Features and weights must have same length');
}
// Normalize weights
$weightSum = array_sum($weights);
$normalizedWeights = array_map(fn($w) => $w / $weightSum, $weights);
// Calculate weighted average
$combinedValue = 0.0;
$combinedBaseline = 0.0;
$combinedStdDev = 0.0;
$hasBaseline = false;
foreach ($features as $i => $feature) {
$combinedValue += $feature->value * $normalizedWeights[$i];
if ($feature->baseline !== null) {
$combinedBaseline += $feature->baseline * $normalizedWeights[$i];
$hasBaseline = true;
}
if ($feature->standardDeviation !== null) {
// Combine standard deviations using root sum of squares
$combinedStdDev += pow($feature->standardDeviation * $normalizedWeights[$i], 2);
}
}
$combinedStdDev = sqrt($combinedStdDev);
return self::create(
type: $features[0]->type,
name: 'combined_' . implode('_', array_map(fn($f) => $f->name, $features)),
value: $combinedValue,
unit: $features[0]->unit,
baseline: $hasBaseline ? $combinedBaseline : null,
standardDeviation: $combinedStdDev > 0 ? $combinedStdDev : null
);
}
/**
* Convert to array for serialization
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'type' => $this->type->value,
'name' => $this->name,
'value' => $this->value,
'unit' => $this->unit,
'baseline' => $this->baseline,
'standard_deviation' => $this->standardDeviation,
'z_score' => $this->zScore,
'normalized_value' => $this->normalizedValue,
'anomaly_score' => $this->getAnomalyScore(),
'is_anomalous' => $this->isAnomalous(),
'deviation_percentage' => $this->getDeviationPercentage(),
'metadata' => $this->metadata,
];
}
/**
* Calculate median from array of values
*
* @param float[] $values
*/
public static function median(array $values): float
{
if (empty($values)) {
return 0.0;
}
sort($values);
$count = count($values);
$middle = (int) floor($count / 2);
if ($count % 2 === 0) {
return ($values[$middle - 1] + $values[$middle]) / 2.0;
}
return $values[$middle];
}
/**
* Calculate standard deviation from array of values
*
* @param float[] $values
*/
public static function standardDeviation(array $values): float
{
if (count($values) < 2) {
return 0.0;
}
$mean = array_sum($values) / count($values);
$variance = array_sum(array_map(fn($v) => pow($v - $mean, 2), $values)) / (count($values) - 1);
return sqrt($variance);
}
/**
* Calculate variance from array of values
*
* @param float[] $values
*/
public static function variance(array $values): float
{
if (count($values) < 2) {
return 0.0;
}
$mean = array_sum($values) / count($values);
return array_sum(array_map(fn($v) => pow($v - $mean, 2), $values)) / (count($values) - 1);
}
}

View File

@@ -0,0 +1,336 @@
<?php
declare(strict_types=1);
namespace App\Framework\MachineLearning\ValueObjects;
/**
* Types of features for ML analysis across different domains
*
* This enum is domain-agnostic and categorizes features by their characteristics
* rather than their specific domain (WAF, Query, Performance, etc.)
*
* Feature categories are organized by:
* - FREQUENCY: Count/rate-based measurements
* - PATTERN: Structural/behavioral patterns
* - TEMPORAL: Time-based patterns
* - PERFORMANCE: Speed/efficiency metrics
* - RESOURCE: Resource utilization metrics
* - ERROR: Error/failure patterns
* - GEOGRAPHIC: Location-based patterns
*/
enum FeatureType: string
{
// Frequency-based features
case FREQUENCY = 'frequency';
case RATE = 'rate';
case VOLUME = 'volume';
// Pattern-based features
case STRUCTURAL_PATTERN = 'structural_pattern';
case BEHAVIORAL_PATTERN = 'behavioral_pattern';
case SEQUENCE_PATTERN = 'sequence_pattern';
case ACCESS_PATTERN = 'access_pattern';
// Temporal features
case TEMPORAL = 'temporal';
case TIME_DISTRIBUTION = 'time_distribution';
case DURATION = 'duration';
// Performance features
case LATENCY = 'latency';
case THROUGHPUT = 'throughput';
case EFFICIENCY = 'efficiency';
// Resource utilization features
case MEMORY_USAGE = 'memory_usage';
case CPU_USAGE = 'cpu_usage';
case CACHE_UTILIZATION = 'cache_utilization';
case CONNECTION_USAGE = 'connection_usage';
// Error/failure features
case ERROR_RATE = 'error_rate';
case FAILURE_PATTERN = 'failure_pattern';
case EXCEPTION_PATTERN = 'exception_pattern';
// Geographic features
case GEOGRAPHIC_DISTRIBUTION = 'geographic_distribution';
case LOCATION_PATTERN = 'location_pattern';
// Session/state features
case SESSION_PATTERN = 'session_pattern';
case STATE_TRANSITION = 'state_transition';
// Diversity/entropy features
case DIVERSITY = 'diversity';
case ENTROPY = 'entropy';
case UNIQUENESS = 'uniqueness';
/**
* Get human-readable description
*/
public function getDescription(): string
{
return match ($this) {
self::FREQUENCY => 'Event occurrence frequency',
self::RATE => 'Event rate over time',
self::VOLUME => 'Total volume or quantity',
self::STRUCTURAL_PATTERN => 'Structural composition patterns',
self::BEHAVIORAL_PATTERN => 'Behavioral characteristic patterns',
self::SEQUENCE_PATTERN => 'Sequential order patterns',
self::ACCESS_PATTERN => 'Access and usage patterns',
self::TEMPORAL => 'Time-based behavior',
self::TIME_DISTRIBUTION => 'Distribution over time',
self::DURATION => 'Time duration measurements',
self::LATENCY => 'Response/execution time',
self::THROUGHPUT => 'Processing throughput rate',
self::EFFICIENCY => 'Efficiency and optimization metrics',
self::MEMORY_USAGE => 'Memory consumption patterns',
self::CPU_USAGE => 'CPU utilization patterns',
self::CACHE_UTILIZATION => 'Cache hit/miss patterns',
self::CONNECTION_USAGE => 'Connection pool usage',
self::ERROR_RATE => 'Error occurrence rate',
self::FAILURE_PATTERN => 'Failure and fault patterns',
self::EXCEPTION_PATTERN => 'Exception occurrence patterns',
self::GEOGRAPHIC_DISTRIBUTION => 'Geographic location distribution',
self::LOCATION_PATTERN => 'Location-based patterns',
self::SESSION_PATTERN => 'Session management patterns',
self::STATE_TRANSITION => 'State change patterns',
self::DIVERSITY => 'Value diversity metrics',
self::ENTROPY => 'Information entropy metrics',
self::UNIQUENESS => 'Uniqueness and cardinality metrics',
};
}
/**
* Get feature extraction weight (importance) for this feature type
*/
public function getWeight(): float
{
return match ($this) {
// High importance (critical features)
self::FREQUENCY, self::ERROR_RATE, self::LATENCY => 0.15,
// Medium-high importance
self::BEHAVIORAL_PATTERN, self::THROUGHPUT, self::FAILURE_PATTERN => 0.12,
// Medium importance
self::RATE, self::TEMPORAL, self::STRUCTURAL_PATTERN,
self::SESSION_PATTERN, self::MEMORY_USAGE => 0.10,
// Medium-low importance
self::VOLUME, self::ACCESS_PATTERN, self::EFFICIENCY,
self::GEOGRAPHIC_DISTRIBUTION => 0.08,
// Lower importance (supplementary features)
self::DURATION, self::CPU_USAGE, self::CACHE_UTILIZATION,
self::SEQUENCE_PATTERN, self::LOCATION_PATTERN => 0.07,
// Low importance (contextual features)
self::TIME_DISTRIBUTION, self::CONNECTION_USAGE,
self::EXCEPTION_PATTERN, self::STATE_TRANSITION,
self::DIVERSITY, self::ENTROPY, self::UNIQUENESS => 0.05,
};
}
/**
* Get minimum sample size needed for reliable analysis
*/
public function getMinSampleSize(): int
{
return match ($this) {
// High sample requirements (time-based patterns)
self::TEMPORAL, self::TIME_DISTRIBUTION => 100,
// Medium-high requirements (frequency/rate based)
self::FREQUENCY, self::RATE, self::LATENCY,
self::SESSION_PATTERN => 50,
// Medium requirements (pattern-based)
self::BEHAVIORAL_PATTERN, self::STRUCTURAL_PATTERN,
self::ERROR_RATE, self::THROUGHPUT => 30,
// Medium-low requirements
self::VOLUME, self::ACCESS_PATTERN, self::FAILURE_PATTERN,
self::MEMORY_USAGE, self::CPU_USAGE => 25,
// Low requirements (simple patterns)
self::DURATION, self::EFFICIENCY, self::CACHE_UTILIZATION,
self::SEQUENCE_PATTERN, self::EXCEPTION_PATTERN,
self::DIVERSITY, self::ENTROPY => 20,
// Minimal requirements
self::GEOGRAPHIC_DISTRIBUTION, self::LOCATION_PATTERN,
self::CONNECTION_USAGE, self::STATE_TRANSITION,
self::UNIQUENESS => 10,
};
}
/**
* Get analysis window duration in seconds
*/
public function getAnalysisWindow(): int
{
return match ($this) {
// 24 hours (daily patterns)
self::TEMPORAL, self::TIME_DISTRIBUTION => 86400,
// 2 hours (long-term trends)
self::GEOGRAPHIC_DISTRIBUTION, self::LOCATION_PATTERN => 7200,
// 1 hour (medium-term patterns)
self::BEHAVIORAL_PATTERN, self::SESSION_PATTERN,
self::STATE_TRANSITION => 3600,
// 30 minutes (short-term patterns)
self::STRUCTURAL_PATTERN, self::ACCESS_PATTERN,
self::DIVERSITY, self::UNIQUENESS => 1800,
// 15 minutes (medium-frequency)
self::VOLUME, self::SEQUENCE_PATTERN, self::ENTROPY,
self::MEMORY_USAGE, self::CPU_USAGE => 900,
// 10 minutes (high-frequency)
self::ERROR_RATE, self::FAILURE_PATTERN, self::EXCEPTION_PATTERN,
self::LATENCY, self::THROUGHPUT => 600,
// 5 minutes (real-time/critical)
self::FREQUENCY, self::RATE, self::EFFICIENCY,
self::CACHE_UTILIZATION, self::CONNECTION_USAGE => 300,
// 2 minutes (very high frequency)
self::DURATION => 120,
};
}
/**
* Check if this feature type requires real-time analysis
*/
public function requiresRealTime(): bool
{
return match ($this) {
self::FREQUENCY,
self::RATE,
self::ERROR_RATE,
self::FAILURE_PATTERN,
self::LATENCY,
self::THROUGHPUT => true,
default => false
};
}
/**
* Check if this feature type is performance-related
*/
public function isPerformanceMetric(): bool
{
return match ($this) {
self::LATENCY,
self::THROUGHPUT,
self::EFFICIENCY,
self::MEMORY_USAGE,
self::CPU_USAGE,
self::CACHE_UTILIZATION,
self::CONNECTION_USAGE,
self::DURATION => true,
default => false
};
}
/**
* Check if this feature type is error/failure-related
*/
public function isErrorMetric(): bool
{
return match ($this) {
self::ERROR_RATE,
self::FAILURE_PATTERN,
self::EXCEPTION_PATTERN => true,
default => false
};
}
/**
* Check if this feature type is pattern-based
*/
public function isPatternMetric(): bool
{
return match ($this) {
self::STRUCTURAL_PATTERN,
self::BEHAVIORAL_PATTERN,
self::SEQUENCE_PATTERN,
self::ACCESS_PATTERN,
self::SESSION_PATTERN,
self::STATE_TRANSITION => true,
default => false
};
}
/**
* Get all feature types as array
*
* @return string[]
*/
public static function getAll(): array
{
return array_map(fn($case) => $case->value, self::cases());
}
/**
* Get real-time feature types
*
* @return self[]
*/
public static function getRealTime(): array
{
return array_filter(self::cases(), fn($case) => $case->requiresRealTime());
}
/**
* Get batch analysis feature types
*
* @return self[]
*/
public static function getBatch(): array
{
return array_filter(self::cases(), fn($case) => !$case->requiresRealTime());
}
/**
* Get performance-related feature types
*
* @return self[]
*/
public static function getPerformanceMetrics(): array
{
return array_filter(self::cases(), fn($case) => $case->isPerformanceMetric());
}
/**
* Get error-related feature types
*
* @return self[]
*/
public static function getErrorMetrics(): array
{
return array_filter(self::cases(), fn($case) => $case->isErrorMetric());
}
/**
* Get pattern-based feature types
*
* @return self[]
*/
public static function getPatternMetrics(): array
{
return array_filter(self::cases(), fn($case) => $case->isPatternMetric());
}
}