Your project uses non-strict array lookups 120
- Read doc
- Reliability
- Major
More information: https://insight.symfony.com/what-we-analyse/php.strict_array_lookup
New rule!
We've recently added this rule to Insight. Don't be surprised to see new suggestions even though the codebase didn't change.
- foreach ($words as $word) {
- // Clean word (remove punctuation)
- $cleanWord = preg_replace('/[^\w\-]/', '', $word);
- // Ignore short words or stop words
- if (strlen($cleanWord) > 2 && !in_array($cleanWord, $stopWords)) {
- $keywords[] = $cleanWord;
- }
- }
- return array_unique($keywords);
- ]
- );
- // Map detection_method to valid ENUM value
- $detectionMethod = $context['detection_method'] ?? 'pattern_based';
- if (!in_array($detectionMethod, ['llm_semantic', 'pattern_based', 'response_validation', 'hybrid'])) {
- $detectionMethod = 'pattern_based'; // default for fallback
- }
- $safeContext = $context;
- unset($safeContext['detection_method']);
- // Insert into database
- $this->db->save($table, $data);
- // Check if we should trigger alerts (for threat events)
- if (in_array($eventType, ['threat_detected', 'threat_blocked']) && isset($details['blocked']) && $details['blocked']) {
- $this->checkAndTriggerAlerts();
- }
- return true;
- } catch (\Exception $e) {
- $severity = 'medium';
- }
- // Map detection_method to valid ENUM value
- $detectionMethod = $context['detection_method'] ?? 'llm_semantic';
- if (!in_array($detectionMethod, ['llm_semantic', 'pattern_based', 'response_validation', 'hybrid'])) {
- $detectionMethod = 'llm_semantic'; // default
- }
- $safeContext = $context;
- unset($safeContext['detection_method']);
- if (in_array($model, ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5-mini'])) {
- $response = $this->handleGpt5Truncation($response);
- }
- // Local models (GPT-OSS, Phi-4, Mistral): May have formatting variations
- if (in_array($model, ['gpt-oss', 'phi-4', 'mistral-large', 'mistral-medium'])) {
- $response = $this->handleLocalModelVariations($response);
- }
- return $response;
- }
- if (isset($response['interpretation'])) {
- $interpretation = $response['interpretation'];
- $lastChar = substr($interpretation, -1);
- // If doesn't end with punctuation, may be truncated
- if (!in_array($lastChar, ['.', '!', '?', ')', ']', '}'])) {
- $response['interpretation'] .= ' [Response may be truncated due to context limit]';
- $response['truncated'] = true;
- }
- }
- {
- if (isset($response['interpretation'])) {
- $interpretation = $response['interpretation'];
- $lastChar = substr($interpretation, -1);
- if (!in_array($lastChar, ['.', '!', '?', ')', ']', '}'])) {
- $response['interpretation'] .= ' [Response may be truncated due to context limit]';
- $response['truncated'] = true;
- }
- }
- * @return array Adjusted response
- */
- private function applyModelSpecificAdjustments(array $response, string $model): array
- {
- // GPT-4.x series: May have truncated responses due to context limits
- if (in_array($model, ['gpt-4', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'])) {
- $response = $this->handleGpt4Truncation($response);
- }
- // GPT-5.x series: Newer models may also hit limits; handle truncation similarly
- if (in_array($model, ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5-mini'])) {
- if (in_array($model, ['gpt-4', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'])) {
- $response = $this->handleGpt4Truncation($response);
- }
- // GPT-5.x series: Newer models may also hit limits; handle truncation similarly
- if (in_array($model, ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5-mini'])) {
- $response = $this->handleGpt5Truncation($response);
- }
- // Local models (GPT-OSS, Phi-4, Mistral): May have formatting variations
- if (in_array($model, ['gpt-oss', 'phi-4', 'mistral-large', 'mistral-medium'])) {
- * @return bool True if analytics response
- */
- private function isAnalyticsResponse(array $response): bool
- {
- $type = $response['response_type'] ?? '';
- return in_array($type, ['analytics_response', 'analytics_results', 'analytics']);
- }
- /**
- * Ensure SQL is extracted for analytics responses
- *
- * @return void
- */
- public function registerEntityTable(string $tableName, string $idColumn, ?string $entityType = null): void
- {
- // Add to entity table cache (IN-MEMORY ONLY)
- if (!in_array($tableName, $this->entityTableCache)) {
- $this->entityTableCache[] = $tableName;
- }
- // Add to ID column cache (IN-MEMORY ONLY)
- $this->idColumnCache[$tableName] = $idColumn;
- * @return bool True if it's a memory table
- */
- public function isMemoryTable(string $tableName): bool
- {
- $memoryTables = $this->getMemoryTables();
- return in_array($tableName, $memoryTables);
- }
- /**
- * Get table metadata
- *
- * @return bool True if it's an entity table
- */
- public function isEntityTable(string $tableName): bool
- {
- $allTables = $this->getAllEntityTables();
- return in_array($tableName, $allTables);
- }
- /**
- * Check if a table is an embedding table
- *
- * @throws NoCapableActorException If no alternative actor available
- */
- private function selectAlternativeActor(Action $action, array $excludeActorIds): ActorAgentInterface
- {
- $capableActors = $this->actorRegistry->getCapableActors($action->getType());
- $alternatives = array_filter($capableActors, fn($a) => !in_array($a->getActorId(), $excludeActorIds));
- if (empty($alternatives)) {
- throw new NoCapableActorException("No alternative actor available for action type: {$action->getType()}");
- }
- ActionResult $result,
- int $count,
- array $excludeCriticIds
- ): array {
- $qualifiedCritics = $this->criticRegistry->getQualifiedCritics($result->getOutputType());
- $validCritics = array_filter($qualifiedCritics, fn($c) => !in_array($c->getCriticId(), $excludeCriticIds));
- if (count($validCritics) < $count) {
- throw new InsufficientCriticsException(
- "Insufficient additional critics available. Needed: {$count}, Available: " . count($validCritics)
- );
- // Get all capable evaluators
- $capableEvaluators = $this->capabilityRegistry->getCapableEvaluators($outputType, 'competent');
- // Filter out excluded evaluators
- $availableEvaluators = array_filter($capableEvaluators, function($evaluator) use ($excludedEvaluators) {
- return !in_array($evaluator['agent_id'], $excludedEvaluators);
- });
- // If no competent evaluators available, try novice level
- if (empty($availableEvaluators)) {
- $capableEvaluators = $this->capabilityRegistry->getCapableEvaluators($outputType, 'novice');
- // If no competent evaluators available, try novice level
- if (empty($availableEvaluators)) {
- $capableEvaluators = $this->capabilityRegistry->getCapableEvaluators($outputType, 'novice');
- $availableEvaluators = array_filter($capableEvaluators, function($evaluator) use ($excludedEvaluators) {
- return !in_array($evaluator['agent_id'], $excludedEvaluators);
- });
- }
- // Return first available evaluator or null
- if (!empty($availableEvaluators)) {
- $hasWarnings = true;
- }
- // Check for flagged or rejected grounding decision
- if (isset($groundingMetadata['grounding_decision']) &&
- in_array($groundingMetadata['grounding_decision'], ['FLAG', 'REJECT'])) {
- $hasWarnings = true;
- }
- if (!$hasWarnings) {
- return '';
- $this->performanceMetrics = array_merge($this->performanceMetrics, $metrics);
- }
- public function supportsParameter(string $parameter): bool
- {
- return in_array($parameter, $this->supportedParameters);
- }
- public function getPerformanceMetric(string $metric): mixed
- {
- return $this->performanceMetrics[$metric] ?? null;
- $weightedSum = 0.0;
- $totalWeight = 0.0;
- foreach ($evaluations as $evaluation) {
- $weight = in_array($evaluation->getEvaluatorAgentId(), $outlierAgents) ? 0.5 : 1.0;
- $weightedSum += $evaluation->getOverallScore() * $weight;
- $totalWeight += $weight;
- }
- $proposedScore = $totalWeight > 0 ? $weightedSum / $totalWeight : array_sum($scores) / count($scores);
- $outlierScores = array_column($outliers, 'score');
- $weightedSum = 0.0;
- $totalWeight = 0.0;
- foreach ($scores as $score) {
- $weight = in_array($score, $outlierScores) ? 0.5 : 1.0;
- $weightedSum += $score * $weight;
- $totalWeight += $weight;
- }
- return $totalWeight > 0 ? $weightedSum / $totalWeight : array_sum($scores) / count($scores);
- return $left;
- };
- $parseAddSub = function() use (&$parseMulDiv, $expr, &$pos) {
- $left = $parseMulDiv();
- while ($pos < strlen($expr) && in_array($expr[$pos], ['+', '-'])) {
- $op = $expr[$pos++];
- $right = $parseMulDiv();
- $left = $op === '+' ? $left + $right : $left - $right;
- }
- return $left;
- return $left;
- };
- $parseMulDiv = function() use (&$parsePower, $expr, &$pos) {
- $left = $parsePower();
- while ($pos < strlen($expr) && in_array($expr[$pos], ['*', '/', '%'])) {
- $op = $expr[$pos++];
- $right = $parsePower();
- $left = match($op) {
- '*' => $left * $right,
- '/' => $right != 0 ? $left / $right : throw new \Exception('Division by zero'),
- }
- // Additional URL validation for allowed schemes
- if (isset($options['allowedSchemes'])) {
- $scheme = parse_url($input, PHP_URL_SCHEME);
- if (!in_array($scheme, $options['allowedSchemes'])) {
- self::logSecurityEvent("URL scheme not allowed: $scheme");
- return $default ?? '';
- }
- }
- }
- // Check file extension if extensions are specified
- if (!empty($allowedExtensions)) {
- $extension = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
- if (!in_array($extension, array_map('strtolower', $allowedExtensions))) {
- self::logSecurityEvent("File has disallowed extension: $extension");
- return false;
- }
- }
- // Check if permission level allows the action
- switch ($permissionLevel) {
- case 'full':
- return true;
- case 'write':
- return in_array($action, ['create', 'read', 'update']);
- case 'read':
- return $action === 'read';
- default:
- return false;
- }
- */
- public function grantDomainPermission(string $agentId, string $domain, string $permissionLevel): bool
- {
- try {
- $validLevels = ['read', 'write', 'full'];
- if (!in_array($permissionLevel, $validLevels)) {
- throw new Exception("Invalid permission level: $permissionLevel");
- }
- // Check if permission already exists
- $Qcheck = $this->db->prepare('
- foreach ($allEntityTypes as $entityType) {
- // Skip system tables that shouldn't be included in context resolution
- // IMPORTANT: Use correct table names with '_embedding' suffix for embedding tables
- // See docs/RAG_TABLE_NAMING_CONVENTION.md for complete documentation
- if (in_array($entityType, [
- 'rag_conversation_memory_embedding', // Embedding table: conversation history
- 'rag_correction_patterns_embedding', // Embedding table: correction patterns
- 'rag_web_cache_embedding', // Embedding table: web cache
- 'rag_memory_retention_log' // System table: retention logs (no embedding)
- ])) {
- }
- }
- // Also try with entity type name directly
- // Matches: "supplier ABC Corp", "manufacturer XYZ Inc"
- if (in_array($entityType, ['suppliers', 'manufacturers', 'categories'])) {
- $namePattern = '/\b(?:' . preg_quote($singularType, '/') . ')\s+(["\']?)([^"\']+)\1/i';
- if (preg_match_all($namePattern, $content, $matches)) {
- if (!isset($entities[$entityType])) {
- $entities[$entityType] = [];
- $type = 'web';
- }
- // Validate response (supports 4 categories)
- $validTypes = ['analytic', 'semantic', 'web', 'hybrid'];
- if (!in_array($type, $validTypes)) {
- if ($this->debug) {
- $this->logWarning("LLM returned invalid type", [
- 'type' => $type,
- 'defaulting_to' => 'semantic'
- ]);
- {
- $validTypes = ['low_reputation', 'rapid_change', 'gaming_detected', 'anomaly'];
- $validSeverities = ['low', 'medium', 'high', 'critical'];
- return !empty($this->criticId)
- && in_array($this->alertType, $validTypes)
- && in_array($this->severity, $validSeverities)
- && !empty($this->message);
- }
- /**
- $validTypes = ['low_reputation', 'rapid_change', 'gaming_detected', 'anomaly'];
- $validSeverities = ['low', 'medium', 'high', 'critical'];
- return !empty($this->criticId)
- && in_array($this->alertType, $validTypes)
- && in_array($this->severity, $validSeverities)
- && !empty($this->message);
- }
- /**
- * Get alert as array for serialization
- // Clamp confidence to [0.0, 1.0]
- $analysis['confidence'] = min(1.0, max(0.0, (float)$analysis['confidence']));
- // Validate threat_type
- $validThreatTypes = ['instruction_override', 'exfiltration', 'hallucination', 'none'];
- if (!\in_array($analysis['threat_type'], $validThreatTypes)) {
- self::$logger->logSecurityEvent(
- "Invalid threat_type: " . $analysis['threat_type'] . ", defaulting to 'none'",
- 'warning'
- );
- $analysis['threat_type'] = 'none';
- case 'analytics':
- // For analytics queries, entities are critical
- if (in_array('entities', $failedOps)) {
- return 'significant'; // Entities are essential for analytics
- }
- if (in_array('memory', $failedOps)) {
- return 'minimal'; // Memory less important for analytics
- }
- return 'minimal';
- case 'hybrid':
- // Assess impact based on query type and failed operations
- switch ($queryType) {
- case 'semantic':
- // For semantic queries, embeddings are critical
- if (in_array('embeddings', $failedOps)) {
- return 'significant'; // Embeddings are essential for semantic search
- }
- if (in_array('memory', $failedOps)) {
- return 'moderate'; // Memory helps but not critical
- }
- case 'semantic':
- // For semantic queries, embeddings are critical
- if (in_array('embeddings', $failedOps)) {
- return 'significant'; // Embeddings are essential for semantic search
- }
- if (in_array('memory', $failedOps)) {
- return 'moderate'; // Memory helps but not critical
- }
- return 'minimal';
- case 'analytics':
- }
- return 'minimal';
- case 'analytics':
- // For analytics queries, entities are critical
- if (in_array('entities', $failedOps)) {
- return 'significant'; // Entities are essential for analytics
- }
- if (in_array('memory', $failedOps)) {
- return 'minimal'; // Memory less important for analytics
- }
- ['modify', 'modify'],
- ['update', 'update']
- ];
- foreach ($conflictingOps as $pair) {
- if ((in_array($pair[0], $operations1) && in_array($pair[1], $operations2)) ||
- (in_array($pair[1], $operations1) && in_array($pair[0], $operations2))) {
- return true;
- }
- }
- ['update', 'update']
- ];
- foreach ($conflictingOps as $pair) {
- if ((in_array($pair[0], $operations1) && in_array($pair[1], $operations2)) ||
- (in_array($pair[1], $operations1) && in_array($pair[0], $operations2))) {
- return true;
- }
- }
- return false;
- ];
- // Extract words
- $words = preg_split('/\s+/', strtolower($text));
- $words = array_filter($words, function($word) use ($stopWords) {
- return strlen($word) > 2 && !in_array($word, $stopWords);
- });
- return array_values($words);
- }
- }
- // Accepter les codes 200, 404 (si /health n'existe pas), ou 405 (méthode non supportée)
- $acceptableCodes = [200, 404, 405];
- if (!in_array($httpCode, $acceptableCodes)) {
- $this->logger->error('HTTP connection test failed', [
- 'http_code' => $httpCode,
- 'response' => $response,
- 'url' => $healthUrl
- ]);
- return in_array($expertise, $this->requiredExpertise);
- }
- public function hasSpecialRequirement(string $requirement): bool
- {
- return in_array($requirement, $this->specialRequirements);
- }
- public function isCritical(): bool
- {
- return $this->priorityLevel === 'critical';
- return $this->priorityLevel === 'critical';
- }
- public function isHighPriority(): bool
- {
- return in_array($this->priorityLevel, ['high', 'critical']);
- }
- public function getMetadataValue(string $key): mixed
- {
- return $this->metadata[$key] ?? null;
- return $this->metadata;
- }
- public function hasRequiredExpertise(string $expertise): bool
- {
- return in_array($expertise, $this->requiredExpertise);
- }
- public function hasSpecialRequirement(string $requirement): bool
- {
- return in_array($requirement, $this->specialRequirements);
- // "recent" is rarely what users want, so it's last
- $priority = ['sum', 'count', 'list', 'recent'];
- $selected = [];
- foreach ($priority as $type) {
- if (in_array($type, $availableInterpretations)) {
- $selected[] = $type;
- if (count($selected) >= $count) {
- break;
- }
- }
- // Check for single day + weekly/monthly aggregation
- if (TemporalConflictPattern::isSingleDayRange($timeRange)) {
- $coarseAggregationPeriods = TemporalConflictPattern::getCoarseAggregationPeriods();
- foreach ($temporalPeriods as $period) {
- if (in_array(strtolower($period), $coarseAggregationPeriods)) {
- return [
- 'has_conflict' => true,
- 'conflict_type' => 'granularity_too_coarse',
- 'conflict_details' => "Cannot aggregate by '{$period}' for a single day ('{$timeRange}'). A single day cannot be broken down into {$period}s.",
- 'suggested_clarification' => "Did you want daily data for '{$timeRange}', or did you mean to specify a longer time range for {$period}ly aggregation?",
- }
- }
- // Fill remaining with any available interpretations
- foreach ($availableInterpretations as $type) {
- if (!in_array($type, $selected)) {
- $selected[] = $type;
- if (count($selected) >= $count) {
- break;
- }
- }
- // Get agent's permission level
- $permissionLevel = $this->getAgentPermissionLevel($agentId, $businessDomain);
- // Actions that always require approval regardless of permission level
- if (in_array($action, $this->approvalRequiredActions)) {
- return true;
- }
- // Propose permission level always requires approval for write actions
- if ($permissionLevel === self::PERMISSION_PROPOSE) {
- ]
- ];
- // Check if action is allowed for this permission level
- if (isset($permissionActions[$permissionLevel])) {
- return in_array($action, $permissionActions[$permissionLevel]);
- }
- // If permission level is unknown, deny access
- return false;
- }
- public function setAgentPermission(string $agentId, string $businessDomain, string $permissionLevel): bool
- {
- $businessDomain = $this->normalizeDomain($businessDomain);
- try {
- // Validate permission level
- if (!in_array($permissionLevel, $this->validPermissionLevels)) {
- throw new Exception("Invalid permission level: $permissionLevel. Must be one of: " .
- implode(', ', $this->validPermissionLevels));
- }
- // Check if permission already exists
- }
- // Execute_safe requires approval for non-safe operations
- if ($permissionLevel === self::PERMISSION_EXECUTE_SAFE) {
- $unsafeActions = ['delete', 'modify_business_rules', 'modify_rules', 'change_business_logic'];
- if (in_array($action, $unsafeActions)) {
- return true;
- }
- }
- return false;
- throw new Exception('Feedback text is required');
- }
- // Validate feedback type
- $validTypes = ['correctness', 'efficiency', 'completeness', 'best_practice'];
- if (!in_array($feedback['feedback_type'], $validTypes)) {
- throw new Exception('Invalid feedback type. Must be one of: ' . implode(', ', $validTypes));
- }
- try {
- $feedbackId = $this->generateFeedbackId();
- $score += 30;
- $factors[] = 'complex_type';
- if ($this->debug) {
- error_log('[INFO] Complexity factor: complex_type (+30)');
- }
- } elseif (in_array($type, ['analytics_results', 'analytics_response'])) {
- $score += 10;
- $factors[] = 'analytics_type';
- if ($this->debug) {
- error_log('[INFO] Complexity factor: analytics_type (+10)');
- }
- $score = 0;
- $factors = [];
- // Factor 1: Result type
- $type = $results['type'] ?? '';
- if (in_array($type, ['complex_query', 'hybrid'])) {
- $score += 30;
- $factors[] = 'complex_type';
- if ($this->debug) {
- error_log('[INFO] Complexity factor: complex_type (+30)');
- }
- throw new \RuntimeException('LLM selected no critics');
- }
- // Validate selected critics are from available pool
- foreach ($data['selected_critics'] as $criticId) {
- if (!in_array($criticId, $availableCriticIds)) {
- throw new \RuntimeException("LLM selected invalid critic: {$criticId}");
- }
- }
- return [
- throw new \RuntimeException("Anomaly {$idx} missing 'description' field");
- }
- // Validate severity is valid
- $validSeverities = ['low', 'medium', 'high'];
- if (!in_array($anomaly['severity'], $validSeverities)) {
- throw new \RuntimeException(
- "Anomaly {$idx} has invalid severity: {$anomaly['severity']}. Must be one of: " .
- implode(', ', $validSeverities)
- );
- }
- $criticId = $critic->getCriticId();
- } elseif (is_array($critic) && isset($critic['critic_id'])) {
- $criticId = $critic['critic_id'];
- }
- if ($criticId && in_array($criticId, $criticIds)) {
- $selected[] = $critic;
- }
- }
- return $selected;
- $updates[] = 'approved_at = :approved_at';
- $params[':approved_at'] = (new DateTime())->format('Y-m-d H:i:s');
- } elseif ($status === 'active') {
- $updates[] = 'started_at = :started_at';
- $params[':started_at'] = (new DateTime())->format('Y-m-d H:i:s');
- } elseif (in_array($status, ['completed', 'failed', 'cancelled'])) {
- $updates[] = 'completed_at = :completed_at';
- $params[':completed_at'] = (new DateTime())->format('Y-m-d H:i:s');
- }
- $sql = "UPDATE :table_rag_agent_objectives
- if ($actualType !== $expectedType) {
- $errors[] = "Expected type '{$expectedType}', got '{$actualType}'";
- }
- // Range validation for numeric types
- if (in_array($expectedType, ['int', 'float'])) {
- if (isset($paramDef['min']) && $value < $paramDef['min']) {
- $errors[] = "Value {$value} is below minimum {$paramDef['min']}";
- }
- if (isset($paramDef['max']) && $value > $paramDef['max']) {
- $errors[] = "Value {$value} is above maximum {$paramDef['max']}";
- $errors[] = "Value {$value} is above maximum {$paramDef['max']}";
- }
- }
- // Enum validation
- if (isset($paramDef['allowed_values']) && !in_array($value, $paramDef['allowed_values'])) {
- $errors[] = "Value '{$value}' is not in allowed values: " . implode(', ', $paramDef['allowed_values']);
- }
- return [
- 'valid' => empty($errors),
- // Type conversion
- if ($key === 'enabled') {
- $config[$key] = (bool)$value;
- } elseif ($key === 'decay_factor') {
- $config[$key] = (float)$value;
- } elseif (in_array($key, ['period_seconds', 'recent_evaluation_count'])) {
- $config[$key] = (int)$value;
- }
- }
- return $config;
- && $this->consistencyScore >= 0.0
- && $this->consistencyScore <= 1.0
- && $this->expertiseAccuracy >= 0.0
- && $this->expertiseAccuracy <= 1.0
- && $this->totalEvaluations >= 0
- && in_array($this->status, ['bootstrapping', 'establishing', 'established']);
- }
- /**
- * Get reputation as array for serialization
- *
- // Handle escaped quotes (e.g., \' or \")
- // Look for patterns like LIKE '%iPhone\'s%'
- $escapedPattern = '/LIKE\s+\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/i';
- if (preg_match_all($escapedPattern, $sql, $matches)) {
- foreach ($matches[1] as $pattern) {
- if (!in_array($pattern, $allMatches)) {
- $allMatches[] = $pattern;
- }
- }
- }
- $relatedTable = $safeRelatedTable;
- }
- $prefix = CLICSHOPPING::getConfig('prefix_table');
- // Check if the related table exists
- if (in_array($relatedTable, $tables) || in_array($prefix . $relatedTable, $tables)) {
- $actualTable = in_array($prefix . $relatedTable, $tables) ? $prefix . $relatedTable : $relatedTable;
- $this->tableRelationships[$table][$column] = $actualTable;
- }
- }
- }
- }
- $prefix = CLICSHOPPING::getConfig('prefix_table');
- // Check if the related table exists
- if (in_array($relatedTable, $tables) || in_array($prefix . $relatedTable, $tables)) {
- $actualTable = in_array($prefix . $relatedTable, $tables) ? $prefix . $relatedTable : $relatedTable;
- $this->tableRelationships[$table][$column] = $actualTable;
- }
- }
- }
- }
- 'score' => $evaluation->getOverallScore()
- ];
- // Collect strengths
- foreach ($evaluation->getStrengths() as $strength) {
- if (!in_array($strength, $allStrengths)) {
- $allStrengths[] = $strength;
- }
- }
- // Collect improvements
- }
- }
- // Collect improvements
- foreach ($evaluation->getImprovements() as $improvement) {
- if (!in_array($improvement, $allImprovements)) {
- $allImprovements[] = $improvement;
- }
- }
- }
- 'semantic_search',
- 'fallback'
- ];
- foreach ($steps as $step) {
- if (!in_array($step->getType(), $allowedTypes)) {
- return false;
- }
- }
- return true;
- $dependsOn = $metadata['depends_on'] ?? [];
- // Filter circular dependencies
- $filteredDependsOn = array_filter($dependsOn, function($depId) use ($cycles, $stepId) {
- foreach ($cycles as $cycle) {
- if (in_array($stepId, $cycle) && in_array($depId, $cycle)) {
- // This dependency creates a cycle
- return false;
- }
- }
- return true;
- {
- $stepIds = array_map(fn($step) => $step->getId(), $steps);
- foreach ($dependencies as $stepId => $deps) {
- foreach ($deps['depends_on'] as $depId) {
- if (!in_array($depId, $stepIds)) {
- return false;
- }
- }
- }
- 'ALTER', 'TRUNCATE', 'REPLACE', 'SHOW', 'DESCRIBE', 'EXPLAIN'
- ];
- $firstWord = strtoupper(explode(' ', $input)[0]);
- if (in_array($firstWord, $sqlKeywords)) {
- return true;
- }
- if (preg_match('/^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|TRUNCATE)\s+/i', $input)) {
- return true;
- // Handles queries where price keyword comes before "on [site]"
- if (preg_match('/\b(what|how|show|get|tell)\s+.*?\s+(on|at)\s+(\w+)/i', $query, $matches)) {
- $site = strtolower($matches[3]);
- // Exclude internal keywords
- if (!in_array($site, WebSearchPatterns::$internalKeywords)) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.95;
- $analysis['override_reason'] = "External site pattern detected: on/at $site";
- $analysis['detection_method'] = 'pattern_post_filter';
- return $analysis;
- // Also matches: "of [product] on [site]", "for [product] on [site]"
- if (preg_match('/\b(price|find|search|look|check|cheaper|available|of|for)\s+.*?\s+(on|at)\s+(\w+)/i', $query, $matches)) {
- $site = strtolower($matches[3]);
- // Exclude internal keywords
- if (!in_array($site, WebSearchPatterns::$internalKeywords)) {
- $analysis['intent_type'] = 'web_search';
- $analysis['confidence'] = 0.95;
- $analysis['override_reason'] = "External site pattern detected: on/at $site";
- $analysis['detection_method'] = 'pattern_post_filter';
- return $analysis;
- 'delete_agent',
- 'modify_security_settings',
- 'access_sensitive_data'
- ];
- if (in_array($actionType, $criticalActions)) {
- return true;
- }
- // Check for repeated violations
- $recentViolations = $this->getRecentViolationCount($agentId);
- 'delete_agent',
- 'modify_security_settings',
- 'access_sensitive_data'
- ];
- if (in_array($actionType, $criticalActions)) {
- return 'critical';
- }
- // High severity actions
- $highSeverityActions = [
- 'approve_objective',
- 'cancel_objective',
- 'modify_agent_role'
- ];
- if (in_array($actionType, $highSeverityActions)) {
- return 'high';
- }
- // Check context for severity indicators
- if (isset($context['repeated_attempt']) && $context['repeated_attempt'] === true) {
- // Filter interpretations to keep only selected types
- $analysis['interpretations'] = array_filter(
- $analysis['interpretations'],
- function($interp) use ($selectedTypes) {
- return in_array($interp['type'] ?? '', $selectedTypes);
- }
- );
- // Re-index array
- $analysis['interpretations'] = array_values($analysis['interpretations']);
- $errors[] = "Step {$index}: Invalid type '{$step['type']}'";
- }
- // Validate unique ID
- if (isset($step['id'])) {
- if (in_array($step['id'], $existingIds)) {
- $errors[] = "Step {$index}: Duplicate ID '{$step['id']}'";
- }
- if (!preg_match('/^step_\d+$/', $step['id'])) {
- $errors[] = "Step {$index}: ID must match pattern 'step_N'";
- // Additional metadata
- if (!empty($metadata)) {
- $parts[] = "Additional Context:";
- foreach ($metadata as $key => $value) {
- if (is_string($value) && !in_array($key, ['entity_type', 'entity_id'])) {
- $parts[] = "- {$key}: {$value}";
- }
- }
- $parts[] = "";
- }
- $errors[] = "Step {$index}: Missing required field '{$field}'";
- }
- }
- // Validate type
- if (isset($step['type']) && !in_array($step['type'], self::ALLOWED_STEP_TYPES)) {
- $errors[] = "Step {$index}: Invalid type '{$step['type']}'";
- }
- // Validate unique ID
- if (isset($step['id'])) {
- $result['errors'][] = "Missing required field: {$field}";
- }
- }
- // 3. Validate complexity
- if (isset($json['complexity']) && !in_array($json['complexity'], self::COMPLEXITY_LEVELS)) {
- $result['errors'][] = "Invalid complexity level: {$json['complexity']}";
- }
- // 4. Validate steps
- if (isset($json['steps'])) {
- if (isset($step['depends_on'])) {
- if (!is_array($step['depends_on'])) {
- $errors[] = "Step {$index}: 'depends_on' must be an array";
- } else {
- foreach ($step['depends_on'] as $depId) {
- if (!in_array($depId, $existingIds)) {
- $errors[] = "Step {$index}: Dependency '{$depId}' references non-existent step";
- }
- }
- }
- }
- // Include outliers with reduced weight
- $totalWeightedScore = 0.0;
- $totalWeight = 0.0;
- foreach ($evaluations as $evaluation) {
- $weight = in_array($evaluation->getEvaluatorAgentId(), $outlierIds) ? 0.3 : 1.0;
- $totalWeightedScore += $evaluation->getOverallScore() * $weight;
- $totalWeight += $weight;
- }
- $finalScore = $totalWeight > 0 ? $totalWeightedScore / $totalWeight : $consensusScore;
- ));
- }
- // Calculate scores excluding outliers
- $nonOutlierEvaluations = array_filter($evaluations, function($evaluation) use ($outlierIds) {
- return !in_array($evaluation->getEvaluatorAgentId(), $outlierIds);
- });
- if (empty($nonOutlierEvaluations)) {
- // All evaluations are outliers, use median
- $allScores = array_map(function($eval) {
- private function mapTemporalColumnName(string $column, string $temporalPeriod, string $languageCode): string
- {
- // Check for period-related columns
- $periodColumns = ['period', 'month', 'quarter', 'semester', 'year', 'week', 'day', 'MONTH', 'QUARTER', 'YEAR', 'WEEK'];
- if (in_array($column, $periodColumns) || stripos($column, 'period') !== false) {
- return $this->getTemporalColumnLabel($temporalPeriod, $languageCode);
- }
- // Standard column name mapping
- return ucwords(str_replace('_', ' ', $column));
- }
- // Check if this is a period column that needs temporal formatting
- $periodColumns = ['period', 'month', 'quarter', 'semester', 'year', 'week', 'day', 'MONTH', 'QUARTER', 'YEAR', 'WEEK'];
- if (in_array($column, $periodColumns) || stripos($column, 'period') !== false) {
- return $this->formatTemporalLabel($temporalPeriod, $value, $languageCode);
- }
- // Format numeric values
- if (is_numeric($value)) {
- }
- return false;
- }
- // Verify type matches requested types
- if (!in_array($subQuery['type'], $requestedTypes)) {
- if ($this->debug) {
- $this->logDebug("Validation failed: sub-query {$index} type '{$subQuery['type']}' not in requested types");
- }
- return false;
- }
- }
- $filteredRow = [];
- foreach ($row as $key => $value) {
- if (!in_array($key, $systemFields)) {
- $filteredRow[$key] = $value;
- }
- }
- return $filteredRow;
- $feedback_type = $row['feedback_type'] ?? '';
- $total += $count;
- if (\in_array($feedback_type, ['positive', 'helpful', 'thumbs_up'])) {
- $positive += $count;
- } elseif (\in_array($feedback_type, ['negative', 'unhelpful', 'thumbs_down'])) {
- $negative += $count;
- }
- }
- $satisfaction_rate = $total > 0 ? round(($positive / $total) * 100, 1) : 0;
- foreach ($results as $row) {
- $count = $row['count'] ?? 0;
- $feedback_type = $row['feedback_type'] ?? '';
- $total += $count;
- if (\in_array($feedback_type, ['positive', 'helpful', 'thumbs_up'])) {
- $positive += $count;
- } elseif (\in_array($feedback_type, ['negative', 'unhelpful', 'thumbs_down'])) {
- $negative += $count;
- }
- }
- ], $meta);
- // Ensure the scope array is initialized (though it should be by createScope/enterScope)
- $this->scopes[$this->currentScope] ??= [];
- if (!in_array($scopedKey, $this->scopes[$this->currentScope])) {
- $this->scopes[$this->currentScope][] = $scopedKey;
- }
- if ($this->debug) {
- $this->securityLogger->logSecurityEvent("WorkingMemory set: {$scopedKey} = " . $this->formatValueForLog($value), 'info');
- if (in_array('analytics', $queryTypes) && in_array('web_search', $queryTypes)) {
- return 'price_comparison';
- }
- // Semantic + analytics
- if (in_array('semantic', $queryTypes) && in_array('analytics', $queryTypes)) {
- return 'semantic_analytics';
- }
- // Default for all other combinations
- return 'default';
- {
- sort($queryTypes);
- $typeKey = implode('_', $queryTypes);
- // Price comparison: analytics + web_search
- if (in_array('analytics', $queryTypes) && in_array('web_search', $queryTypes)) {
- return 'price_comparison';
- }
- // Semantic + analytics
- if (in_array('semantic', $queryTypes) && in_array('analytics', $queryTypes)) {
- if (!isset($columns[$table])) {
- $columns[$table] = [];
- }
- if (!in_array($column, $columns[$table])) {
- $columns[$table][] = $column;
- }
- }
- }
- }
- private function getFrequentlyQueriedColumns(string $table): array
- {
- $columns = [];
- foreach ($this->slowQueries as $query) {
- if (in_array($table, $query['analysis']['tables_accessed'])) {
- $whereColumns = $this->extractWhereColumns($query['sql']);
- if (isset($whereColumns[$table])) {
- foreach ($whereColumns[$table] as $column) {
- if (!isset($columns[$column])) {
- $columns[$column] = 0;
- * @return bool True if index exists
- */
- private function hasIndexOnColumn(array $indexes, string $column): bool
- {
- foreach ($indexes as $index) {
- if (in_array($column, $index['columns'])) {
- return true;
- }
- }
- return false;
- }
- }
- break;
- }
- // Check for "per {period}" pattern
- if (preg_match('/\bper\s+' . preg_quote($variant, '/') . '\b/i', $query)) {
- if (!in_array($basePeriod, $detected)) {
- $detected[] = $basePeriod;
- }
- break;
- }
- }
- // First pass: Check for "by {period}" or "per {period}" patterns
- foreach ($basePeriods as $basePeriod => $variants) {
- foreach ($variants as $variant) {
- // Check for "by {period}" pattern
- if (preg_match('/\bby\s+' . preg_quote($variant, '/') . '\b/i', $query)) {
- if (!in_array($basePeriod, $detected)) {
- $detected[] = $basePeriod;
- }
- break;
- }
- // Check for "per {period}" pattern
- foreach ($basePeriods as $basePeriod => $variants) {
- foreach ($variants as $variant) {
- // Check if the period appears anywhere in the query
- if (preg_match('/\b' . preg_quote($variant, '/') . '\b/i', $query)) {
- if (!in_array($basePeriod, $detected)) {
- $detected[] = $basePeriod;
- }
- break;
- }
- }
- ]);
- $this->workingMemory->set('reasoning_result', $reasoning);
- // default to semantic (safer fallback than analytics)
- if ($intent['confidence'] < 0.6 && !in_array($intent['type'], ['analytics', 'semantic', 'web_search', 'hybrid'])) {
- $this->securityLogger->logStructured(
- 'warning',
- 'OrchestratorAgent',
- 'fallback_to_semantic',
- [
- $hasWebSearchKeyword = true;
- break;
- }
- }
- if ($hasWebSearchKeyword && !in_array('web_search', $intent['sub_types'])) {
- $intent['sub_types'][] = 'web_search';
- if ($this->debug) {
- $this->securityLogger->logStructured('info', 'OrchestratorAgent', 'web_search_detection_fallback', [
- 'query' => substr($queryToProcess, 0, 100),
- break;
- }
- }
- // If web search keyword found but not in sub_types, add it
- if ($hasWebSearchKeyword && !in_array('web_search', $intent['sub_types'])) {
- $intent['sub_types'][] = 'web_search';
- if ($this->debug) {
- $this->securityLogger->logStructured('info', 'OrchestratorAgent', 'web_search_detection', [
- 'query' => substr($queryToProcess, 0, 100),
- // À implémenter avec Slack API
- });
- // Canal: SMS (placeholder)
- $this->addNotificationChannel('sms', function($alert) {
- if (in_array($alert['severity'], [self::SEVERITY_ERROR, self::SEVERITY_CRITICAL])) {
- // À implémenter avec votre système SMS
- }
- });
- }
- }
- $targetTable = $prefix . $pluralEntity . '_embedding';
- // Only search in the specific table if it exists
- if (in_array($targetTable, $embeddingTables)) {
- $embeddingTables = [$targetTable];
- if ($this->debug) {
- $this->logger->logSecurityEvent(
- "Filtered to specific entity table: {$targetTable}",
- self::CONFIG_KEY_REPUTATION_DECAY_RECENT_EVAL_COUNT,
- self::CONFIG_KEY_REPUTATION_BOOTSTRAP_THRESHOLD_LOW,
- self::CONFIG_KEY_REPUTATION_BOOTSTRAP_THRESHOLD_HIGH
- ])) {
- $config[$key] = (int)$value;
- } elseif (in_array($key, [
- self::CONFIG_KEY_CONSENSUS_THRESHOLD,
- self::CONFIG_KEY_REPUTATION_DECAY_FACTOR,
- self::CONFIG_KEY_REPUTATION_DECAY_RATE,
- self::CONFIG_KEY_REPUTATION_WEIGHT_CONSENSUS,
- self::CONFIG_KEY_REPUTATION_WEIGHT_FEEDBACK,
- // Type conversion
- if ($key === self::CONFIG_KEY_ENABLED ||
- $key === self::CONFIG_KEY_FALLBACK_TO_HYBRID ||
- $key === self::CONFIG_KEY_REPUTATION_DECAY_ENABLED) {
- $config[$key] = self::parseBool($value);
- } elseif (in_array($key, [
- self::CONFIG_KEY_CRITICS_PER_EVALUATION,
- self::CONFIG_KEY_MIN_CRITICS_REQUIRED,
- self::CONFIG_KEY_ACTOR_RETRY_ATTEMPTS,
- self::CONFIG_KEY_CRITIC_EVALUATION_TIMEOUT,
- self::CONFIG_KEY_MAX_CONCURRENT_ACTIONS_PER_ACTOR,
- }
- public function canHandle(array $results): bool
- {
- $type = $results['type'] ?? '';
- return in_array($type, ['analytics_results', 'analytics_response']);
- }
- public function format(array $results): array
- {
- $question = $results['question'] ?? $results['query'] ?? 'Unknown request';
- public function isHealthy(): bool
- {
- $snapshot = $this->collectMetrics();
- $health = $this->calculateOverallHealth($snapshot);
- return in_array($health['status'], ['healthy', 'warning']);
- }
- /**
- * Gets API metrics
- *
- // Moderate divergence - use weighted average excluding outliers
- $outliers = $context['outliers'] ?? [];
- $outlierScores = array_column($outliers, 'score');
- $validScores = array_filter($scores, function($score) use ($outlierScores) {
- return !in_array($score, $outlierScores);
- });
- if (!empty($validScores)) {
- $finalScore = array_sum($validScores) / count($validScores);
- $reasoning[] = "Moderate divergence detected (agreement: " . round($agreementLevel * 100, 1) . "%)";
- bool $enabled = true
- ): bool {
- self::initialize();
- // Validate type
- if (!in_array($type, [self::AGENT_TYPE_ACTOR, self::AGENT_TYPE_CRITIC, self::AGENT_TYPE_HYBRID])) {
- if (self::$debug) {
- error_log("AgentActivationConfig: Invalid agent type: {$type}");
- }
- return false;
- }
- }
- $sourceAttr = $result['source_attribution'];
- // If source is RAG, verify embedding data exists
- if (in_array($sourceAttr['source_type'], ['rag', 'semantic', 'embedding'])) {
- if (!$this->hasEmbeddingData($result)) {
- $validationErrors[] = "RAG source but no document/embedding data found";
- return false;
- }
- }
- throw new \Exception('Missing required fields in JSON response');
- }
- // Validate type (must be one of 4 categories)
- $validTypes = ['analytics', 'semantic', 'hybrid', 'web_search'];
- if (!in_array($result['type'], $validTypes)) {
- throw new \Exception('Invalid type: ' . $result['type']);
- }
- // Validate confidence (must be between 0.0 and 1.0)
- $confidence = (float)$result['confidence'];
- $response = Gpt::getGptResponse($prompt, 20);
- $type = trim(strtolower($response));
- // Validate old prompt response - default to 'semantic' if invalid
- if (!in_array($type, ['analytics', 'semantic'])) {
- $type = 'semantic'; // Default fallback
- }
- // Return in new format with default values
- return [
- $domains = [];
- foreach ($criteria as $outputType => $criterion) {
- if (is_object($criterion) && method_exists($criterion, 'getDomain')) {
- $domain = $criterion->getDomain();
- if (!empty($domain) && !in_array($domain, $domains)) {
- $domains[] = $domain;
- }
- }
- }
- * @throws \InvalidArgumentException If agent type is invalid
- */
- public function getSystemMessage(string $agentType = 'analytics', string $query = '', string $modelName = 'gpt-4o-mini'): string
- {
- // Validate agent type
- if (!in_array($agentType, self::AGENT_TYPES)) {
- throw new \InvalidArgumentException("Invalid agent type: {$agentType}. Supported types: " . implode(', ', self::AGENT_TYPES));
- }
- // Store parameters for buildSystemMessage()
- $this->agentType = $agentType;
- public function canHandle(array $results): bool
- {
- $type = $results['type'] ?? '';
- // Note: 'hybrid' is now handled by HybridFormatter (priority 105)
- // ComplexQueryFormatter handles 'complex_query' and legacy 'hybrid_results'
- return in_array($type, ['complex_query', 'hybrid_results']);
- }
- /**
- * Format complex query results for display
- *
- error_log("ConversationMemory::recordFeedback - Interaction ID: {$interactionId}");
- error_log("ConversationMemory::recordFeedback - Feedback Type: {$feedbackType}");
- // Validate feedback type
- $validTypes = ['positive', 'negative', 'correction'];
- if (!in_array($feedbackType, $validTypes)) {
- error_log("ConversationMemory::recordFeedback - INVALID TYPE: {$feedbackType}");
- $this->securityLogger->logSecurityEvent(
- "Invalid feedback type: {$feedbackType}. Must be one of: " . implode(', ', $validTypes),
- 'warning'
- );
- */
- public function setDefaultEngine(string $engine): void
- {
- $allowed = ['google', 'bing', 'duckduckgo', 'yahoo'];
- if (!in_array($engine, $allowed)) {
- throw new \InvalidArgumentException("Engine must be one of: " . implode(', ', $allowed));
- }
- $this->defaultEngine = $engine;
- }
- // Extract type
- $type = $classificationResult['type'] ?? 'semantic';
- // Validate response (now supports 4 categories)
- $validTypes = ['analytics', 'semantic', 'hybrid', 'web_search'];
- if (!in_array($type, $validTypes)) {
- if ($this->debug) {
- $this->logger->logStructured(
- 'warning',
- 'QueryClassifier',
- public function recordFeedback(string $interactionId,string $feedbackType, array $feedbackData): bool
- {
- try {
- // Validate feedback type
- $validTypes = ['positive', 'negative', 'correction'];
- if (!in_array($feedbackType, $validTypes)) {
- throw new \InvalidArgumentException("Invalid feedback type: {$feedbackType}");
- }
- // Extract required data
- $userId = $feedbackData['user_id'] ?? 'unknown';
Your project should not use deprecated PHP features 5
- Read doc
- Productivity
- Major
More information: https://insight.symfony.com/what-we-analyse/php.use_deprecated_function
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- $info = curl_getinfo($ch);
- curl_close($ch);
- $this->stats['messages_sent']++;
- $this->stats['last_activity'] = time();
- $response = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- $info = curl_getinfo($ch);
- curl_close($ch);
- if ($error) {
- $this->logger->error('cURL connection error', ['error' => $error, 'url' => $healthUrl]);
- throw new McpConnectionException("Connection failed: {$error}");
- }
- if ($ext != 'webp') {
- if ($img = imagecreatefromstring(file_get_contents($big_image_resized_path))) {
- $image = str_replace($ext, 'webp', $image);
- $this->imageResample->save($this->template->getDirectoryPathTemplateShopImages() . $image, $ext);
- imagedestroy($img);
- }
- if (file_exists($big_image_resized_path)) {
- unlink($big_image_resized_path);
- }
- $result = curl_exec($curl);
- if (empty($result)) {
- $info = curl_getinfo($curl);
- curl_close($curl);
- } else {
- $info = 'error';
- }
- return $info;
- $tmp = tempnam('.', 'gif');
- if (!$tmp)
- $this->Error('Unable to create a temporary file');
- if (!imagepng($im, $tmp))
- $this->Error('Error while saving to temporary file');
- imagedestroy($im);
- $info = $this->_parsepng($tmp);
- unlink($tmp);
- return $info;
- }
Mutable DateTime should be avoided in favor of DateTimeImmutable 38
- Read doc
- Reliability
- Minor
More information: https://insight.symfony.com/what-we-analyse/php.prefer_date_time_immutable
New rule!
We've recently added this rule to Insight. Don't be surprised to see new suggestions even though the codebase didn't change.
- criticScore: (float)$data['critic_score'],
- consensusScore: (float)$data['consensus_score'],
- withinThreshold: (bool)$data['within_threshold'],
- alignmentDelta: (float)$data['alignment_delta'],
- feedbackAccepted: (bool)$data['feedback_accepted'],
- evaluatedAt: new \DateTime($data['evaluated_at']),
- metadata: $data['metadata'] ?? []
- );
- }
- }
- *
- * @return int
- */
- public function getElapsedTime(): int
- {
- $now = new DateTime();
- return $now->getTimestamp() - $this->createdAt->getTimestamp();
- }
- /**
- * Get the remaining time until estimated completion in seconds
- updated_at = :updated_at
- WHERE expertise_level = :expertise_level";
- $stmt = $this->db->prepare($sql);
- $stmt->bindValue(':expertise_weight', (float)$weight);
- $stmt->bindValue(':updated_at', (new DateTime())->format('Y-m-d H:i:s'));
- $stmt->bindValue(':expertise_level', $level);
- $stmt->execute();
- if ($this->debug) {
- error_log(sprintf(
- $interval = $startDate->diff($endDate);
- return [
- 'report_id' => $this->generateUuid(),
- 'report_type' => $reportType,
- 'generated_at' => (new DateTime())->format('Y-m-d H:i:s'),
- 'period' => [
- 'start_date' => $startDate->format('Y-m-d H:i:s'),
- 'end_date' => $endDate->format('Y-m-d H:i:s'),
- 'duration_days' => $interval->days,
- 'duration_hours' => ($interval->days * 24) + $interval->h
- $alert->criticId = $criticId;
- $alert->alertType = $alertType;
- $alert->severity = $severity;
- $alert->message = $message;
- $alert->context = $context;
- $alert->createdAt = new DateTime();
- return $alert;
- }
- /**
- $this->version = $version;
- $this->senderId = $senderId;
- $this->recipientId = $recipientId;
- $this->payload = $payload;
- $this->correlationId = $correlationId;
- $this->timestamp = new \DateTime();
- $this->retryCount = 0;
- $this->metadata = [
- 'created_at' => $this->timestamp->format('Y-m-d H:i:s.u'),
- 'protocol_version' => $version
- ];
- }
- // Distribute feedback into weekly buckets
- foreach ($feedbackItems as $item) {
- $createdAt = new DateTime($item['created_at']);
- $now = new DateTime();
- $daysDiff = $now->diff($createdAt)->days;
- $weekIndex = floor($daysDiff / 7);
- if ($weekIndex < $weeks) {
- $weekKey = "week_" . ($weeks - $weekIndex);
- *
- * @return int Elapsed time in seconds
- */
- public function getElapsedSeconds(): int
- {
- $now = new DateTime();
- return $now->getTimestamp() - $this->startedAt->getTimestamp();
- }
- /**
- * Gets the remaining time before timeout
- }
- $this->createdAt = new DateTime($row['created_at']);
- if ($row['completed_at']) {
- $this->completedAt = new DateTime($row['completed_at']);
- }
- if ($row['metrics']) {
- $this->metrics = json_decode($row['metrics'], true);
- }
- $this->evaluations = $evaluations;
- $this->consensusScore = $consensusScore;
- $this->consensusReached = $consensusReached;
- $this->aggregatedFeedback = $aggregatedFeedback;
- $this->outliers = $outliers;
- $this->createdAt = new \DateTime();
- }
- public function getConsensusId(): string { return $this->consensusId; }
- public function getOutputId(): string { return $this->outputId; }
- public function getEvaluations(): array { return $this->evaluations; }
- $reputation->consistencyScore = 0.75;
- $reputation->expertiseAccuracy = 0.75;
- $reputation->totalEvaluations = 0;
- $reputation->status = 'bootstrapping';
- $reputation->calculatedAt = new DateTime();
- $reputation->lastDecayAt = new DateTime();
- return $reputation;
- }
- /**
- $this->finalScore = $finalScore;
- $this->agreementLevel = $agreementLevel;
- $this->outliers = $outliers;
- $this->discussionLog = $discussionLog;
- $this->createdAt = new DateTime();
- $this->resolvedAt = $consensusReached ? new DateTime() : null;
- }
- /**
- * Generates a unique session ID
- *
- */
- private function logOperation(string $operation, array $data): void
- {
- $this->normalizationLog[] = [
- 'operation' => $operation,
- 'timestamp' => (new \DateTime())->format('Y-m-d H:i:s.u'),
- 'data' => $data
- ];
- }
- }
- $stmt = $this->db->prepare($sql);
- $stmt->bindValue(':agent_id', $agentId);
- $stmt->bindValue(':output_type', $outputType);
- $stmt->bindValue(':capability_level', $level);
- $stmt->bindValue(':updated_at', (new DateTime())->format('Y-m-d H:i:s'));
- $stmt->execute();
- } else {
- // Create new capability
- $this->registerCapability($agentId, $outputType, $level);
- }
- $this->clarityScore = $scores['clarity'];
- $this->overallScore = $this->calculateOverallScore($scores);
- $this->feedback = $feedback;
- $this->strengths = $strengths;
- $this->improvements = $improvements;
- $this->evaluatedAt = new \DateTime();
- }
- private function calculateOverallScore(array $scores): float
- {
- // Weighted average: accuracy (35%), completeness (25%), efficiency (25%), clarity (15%)
- $this->predictedOutcome = $predictedOutcome;
- $this->confidenceEstimate = max(0.0, min(1.0, $confidenceEstimate));
- $this->identifiedRisks = $identifiedRisks;
- $this->successProbabilities = $successProbabilities;
- $this->recommendedMitigations = $recommendedMitigations;
- $this->predictedAt = new \DateTime();
- }
- public function getPredictionId(): string { return $this->predictionId; }
- public function getActionId(): string { return $this->actionId; }
- public function getPredictorAgentId(): string { return $this->predictorAgentId; }
- // Generate recommendations
- $recommendations = $this->generateRecommendations($correlation, $qualityTrends, $weightingImpact);
- $report = [
- 'period' => 'Last 30 days',
- 'generated_at' => (new DateTime())->format('Y-m-d H:i:s'),
- 'total_consensus_operations' => count($monthlyConsensus),
- 'correlation_analysis' => $correlation,
- 'quality_trends' => $qualityTrends,
- 'weighting_impact' => $weightingImpact,
- 'recommendations' => $recommendations
- * @return string
- */
- private function formatDate(string $dateString): string
- {
- try {
- $dt = new \DateTime($dateString);
- return $dt->format('d/m/Y H:i');
- } catch (\Throwable) {
- return $dateString;
- }
- }
- $outcome->criticScore = (float)($data['criticScore'] ?? $data['critic_score'] ?? 0.0);
- $outcome->consensusScore = (float)($data['consensusScore'] ?? $data['consensus_score'] ?? 0.0);
- $outcome->withinThreshold = (bool)($data['withinThreshold'] ?? $data['within_threshold'] ?? false);
- $outcome->alignmentDelta = (float)($data['alignmentDelta'] ?? $data['alignment_delta'] ?? 0.0);
- $outcome->feedbackAccepted = (bool)($data['feedbackAccepted'] ?? $data['feedback_accepted'] ?? false);
- $outcome->evaluatedAt = new \DateTime($data['evaluatedAt'] ?? $data['evaluated_at'] ?? 'now');
- return $outcome;
- }
- /**
- $this->producerAgentId = $producerAgentId;
- $this->output = $output;
- $this->outputType = $outputType;
- $this->executionMetrics = $executionMetrics;
- $this->executionContext = $executionContext;
- $this->timestamp = new \DateTime();
- $this->status = $status;
- }
- public function getResultId(): string { return $this->resultId; }
- public function getActionId(): string { return $this->actionId; }
- }
- public function acknowledge(): void
- {
- $this->acknowledged = true;
- $this->acknowledgedAt = new \DateTime();
- }
- public function getFeedbackId(): string { return $this->feedbackId; }
- public function getTargetActorId(): string { return $this->targetActorId; }
- public function getOutputId(): string { return $this->outputId; }
- $this->staticConsensus = $staticConsensus;
- $this->consensusDifference = $consensusDifference;
- $this->weightedScores = $weightedScores;
- $this->confidenceLevel = $confidenceLevel;
- $this->consensusQuality = $consensusQuality;
- $this->calculatedAt = new \DateTime();
- }
- public function getEvaluationId(): string
- {
- return $this->evaluationId;
- * @return string
- */
- private function formatDate(string $dateString): string
- {
- try {
- $dt = new \DateTime($dateString);
- return $dt->format('d/m/Y H:i');
- } catch (\Throwable) {
- return $dateString;
- }
- }
- $this->evaluatorAgentId = $evaluatorAgentId;
- $this->outputId = $outputId;
- $this->feedback = $feedback;
- $this->strengths = $strengths;
- $this->improvements = $improvements;
- $this->evaluatedAt = new DateTime();
- }
- /**
- * Validates a score value
- *
- 'consistencyScore' => (float)$row['consistency_score'],
- 'expertiseAccuracy' => (float)$row['expertise_accuracy'],
- 'totalEvaluations' => (int)$row['total_evaluations'],
- 'status' => $row['status'],
- 'calculatedAt' => new \DateTime($row['calculated_at']),
- 'lastDecayAt' => new \DateTime($row['last_decay_at'])
- ];
- }
- return $reputations;
- }
- $this->normalizedWeights = $normalizedWeights;
- $this->explanations = $explanations;
- $this->overallRationale = $overallRationale;
- $this->factorAnalysis = $factorAnalysis;
- $this->bounds = $bounds;
- $this->calculatedAt = new \DateTime();
- $this->isFallback = $isFallback;
- $this->fallbackReason = $fallbackReason;
- }
- public function getEvaluationId(): string
- try {
- $Q = $this->db->prepare('SELECT date_created FROM :table_products_cockpit_ai_rule_adjustments WHERE rule_key = :key ORDER BY date_created DESC LIMIT 1');
- $Q->bindValue(':key', $ruleKey);
- $Q->execute();
- if (!$Q->fetch()) return true;
- return (new \DateTime())->diff(new \DateTime($Q->value('date_created')))->days >= 7;
- } catch (\Throwable) {
- return true;
- }
- }
- $days = 1;
- if ($startDate && $endDate) {
- $interval = $startDate->diff($endDate);
- $days = max(1, $interval->days);
- } elseif ($startDate) {
- $interval = $startDate->diff(new DateTime());
- $days = max(1, $interval->days);
- }
- // Get breakdown by evaluator
- $byEvaluator = $this->getEvaluationBreakdown('evaluator_agent_id', $whereClause, $params);
- $history->criticScore = $criticScore;
- $history->alignmentDelta = abs($consensusScore - $criticScore);
- $history->reputationImpact = $newReputation - $oldReputation;
- $history->oldReputation = $oldReputation;
- $history->newReputation = $newReputation;
- $history->recordedAt = new DateTime();
- return $history;
- }
- /**
- $history->criticScore = $criticScore;
- $history->alignmentDelta = $alignmentDelta;
- $history->reputationImpact = $newReputation - $oldReputation;
- $history->oldReputation = $oldReputation;
- $history->newReputation = $newReputation;
- $history->recordedAt = new DateTime();
- $this->store->saveHistory($history);
- }
- /**
- $alert->message = $row['message'];
- $alert->context = $row['context'] ? json_decode($row['context'], true) : null;
- $alert->acknowledged = (bool)$row['acknowledged'];
- $alert->acknowledgedBy = $row['acknowledged_by'];
- $alert->acknowledgedAt = $row['acknowledged_at'] ? new \DateTime($row['acknowledged_at']) : null;
- $alert->createdAt = new \DateTime($row['created_at']);
- return $alert;
- }
- /**
- $this->userId = $userId;
- $this->languageId = $languageId;
- $this->systemState = $systemState;
- $this->userPreferences = $userPreferences;
- $this->environmentalData = $environmentalData;
- $this->timestamp = new \DateTime();
- }
- public function getContextId(): string { return $this->contextId; }
- public function getUserId(): string { return $this->userId; }
- public function getLanguageId(): int { return $this->languageId; }
- $factors['seo_score'] = new ScoreFactor(null, 100.0, notAnalyzed: true);
- }
- // REQ-SC-01 : Récupération de l'âge du produit et du max catalogue
- $dateAdded = new \DateTime($product['products_date_added'] ?? 'now');
- $ageDays = (int) $dateAdded->diff(new \DateTime())->format('%a');
- $maxDays = $context->catalog->ageMax ?? 365;
- // Ajout du facteur de fraîcheur
- $factors['creation_date'] = new CreationDateFactor($ageDays, $maxDays);
- criticScore: (float)$job['critic_score'],
- consensusScore: (float)$job['consensus_score'],
- withinThreshold: abs($job['critic_score'] - $job['consensus_score']) < 0.1,
- alignmentDelta: (float)$job['alignment_delta'],
- feedbackAccepted: (bool)$job['feedback_accepted'],
- evaluatedAt: new \DateTime(),
- metadata: json_decode($job['metadata'], true) ?? []
- );
- // Process reputation update
- $this->reputationTracker->trackEvaluation($outcome);
- $history->criticScore = (float)$row['critic_score'];
- $history->alignmentDelta = (float)$row['alignment_delta'];
- $history->reputationImpact = (float)$row['reputation_impact'];
- $history->oldReputation = (float)$row['old_reputation'];
- $history->newReputation = (float)$row['new_reputation'];
- $history->recordedAt = new \DateTime($row['recorded_at']);
- return $history;
- }
- /**
- $this->evaluations = $evaluations;
- $this->reputations = $reputations;
- $this->agreementLevel = $agreementLevel;
- $this->confidence = $confidence;
- $this->stability = $stability;
- $this->calculatedAt = new \DateTime();
- }
- /**
- * Get consensus result as array for serialization
- *
- 'criticScore' => (float)$row['critic_score'],
- 'consensusScore' => (float)$row['consensus_score'],
- 'withinThreshold' => (bool)$row['within_threshold'],
- 'alignmentDelta' => (float)$row['alignment_delta'],
- 'feedbackAccepted' => (bool)$row['feedback_accepted'],
- 'evaluatedAt' => new \DateTime($row['evaluated_at'])
- ];
- }
- return $outcomes;
- }
- // Generate alerts
- $alertCount = $this->generateAlerts($allIssues);
- return [
- 'detection_run_at' => (new DateTime())->format('Y-m-d H:i:s'),
- 'period' => [
- 'start_date' => $startDate ? $startDate->format('Y-m-d H:i:s') : 'all time',
- 'end_date' => $endDate ? $endDate->format('Y-m-d H:i:s') : 'now'
- ],
- 'issues_detected' => [