apiKey = ANTHROPIC_API_KEY; $this->model = ANTHROPIC_MODEL; $this->maxTokens = ANTHROPIC_MAX_TOKENS; if (empty($this->apiKey) || $this->apiKey === 'sk-ant-xxxxx') { throw new RuntimeException('ANTHROPIC_API_KEY non configurata'); } } /** * Analizza risultati gap analysis e genera raccomandazioni */ public function analyzeGapAssessment(array $organization, array $responses, float $overallScore): array { $responseSummary = $this->summarizeResponses($responses); // Anonimizzazione: non inviare nome org né fatturato esatto ad API esterna $employeeRange = $this->employeeRange((int)($organization['employee_count'] ?? 0)); $prompt = <<callAPI($prompt); return $this->parseJsonResponse($response); } /** * Suggerisce rischi basati su settore e asset */ public function suggestRisks(array $organization, array $assets = []): array { $assetList = empty($assets) ? 'Non disponibile' : json_encode($assets, JSON_UNESCAPED_UNICODE); $prompt = <<callAPI($prompt); return $this->parseJsonResponse($response); } /** * Genera bozza di policy */ public function generatePolicy(string $category, array $organization, ?array $assessmentContext = null): array { $context = $assessmentContext ? json_encode($assessmentContext, JSON_UNESCAPED_UNICODE) : 'Non disponibile'; $prompt = <<callAPI($prompt); return $this->parseJsonResponse($response); } /** * Classifica un incidente e suggerisce severity */ public function classifyIncident(string $title, string $description, array $organization): array { $prompt = <<callAPI($prompt); return $this->parseJsonResponse($response); } /** * Chiama Anthropic API */ private function callAPI(string $prompt, ?string $systemPrompt = null): string { $system = $systemPrompt ?? 'Sei un esperto consulente di cybersecurity e compliance NIS2. Rispondi sempre in italiano in modo professionale e accurato.'; $body = [ 'model' => $this->model, 'max_tokens' => $this->maxTokens, 'system' => $system, 'messages' => [ ['role' => 'user', 'content' => $prompt], ], ]; $ch = curl_init($this->baseUrl); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-api-key: ' . $this->apiKey, 'anthropic-version: 2023-06-01', ], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($body), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 120, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($curlError) { throw new RuntimeException('Errore connessione AI: ' . $curlError); } if ($httpCode !== 200) { $errorData = json_decode($response, true); $errorMessage = $errorData['error']['message'] ?? 'Errore API sconosciuto'; throw new RuntimeException("Errore API AI ({$httpCode}): {$errorMessage}"); } $data = json_decode($response, true); if (!isset($data['content'][0]['text'])) { throw new RuntimeException('Risposta AI non valida'); } return $data['content'][0]['text']; } /** * Converte numero dipendenti in range anonimizzato */ private function employeeRange(int $count): string { if ($count <= 0) return 'Non specificato'; if ($count <= 10) return 'Micro impresa (1-10 dipendenti)'; if ($count <= 50) return 'Piccola impresa (11-50 dipendenti)'; if ($count <= 250) return 'Media impresa (51-250 dipendenti)'; if ($count <= 1000) return 'Grande impresa (251-1000 dipendenti)'; return 'Grande organizzazione (>1000 dipendenti)'; } /** * Riassume le risposte dell'assessment per il prompt AI */ private function summarizeResponses(array $responses): string { $byCategory = []; foreach ($responses as $r) { $cat = $r['category'] ?? 'other'; if (!isset($byCategory[$cat])) { $byCategory[$cat] = ['implemented' => 0, 'partial' => 0, 'not_implemented' => 0, 'na' => 0, 'total' => 0]; } $byCategory[$cat]['total']++; match ($r['response_value']) { 'implemented' => $byCategory[$cat]['implemented']++, 'partial' => $byCategory[$cat]['partial']++, 'not_implemented' => $byCategory[$cat]['not_implemented']++, 'not_applicable' => $byCategory[$cat]['na']++, default => null, }; } $summary = ''; foreach ($byCategory as $cat => $counts) { $pct = $counts['total'] > 0 ? round(($counts['implemented'] * 100 + $counts['partial'] * 50) / (($counts['total'] - $counts['na']) * 100) * 100) : 0; $summary .= "- {$cat}: {$pct}% (implementati: {$counts['implemented']}, parziali: {$counts['partial']}, non implementati: {$counts['not_implemented']})\n"; } return $summary; } /** * Parsing robusto della risposta JSON dall'AI */ private function parseJsonResponse(string $response): array { // Rimuovi eventuale markdown code blocks $response = preg_replace('/^```(?:json)?\s*/m', '', $response); $response = preg_replace('/\s*```\s*$/m', '', $response); $response = trim($response); $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log('[AI] JSON parse error: ' . json_last_error_msg() . ' | Response: ' . substr($response, 0, 500)); return ['error' => 'Impossibile analizzare la risposta AI', 'raw' => substr($response, 0, 1000)]; } return $data; } /** * Registra interazione AI nel database */ public function logInteraction(int $orgId, int $userId, string $type, string $promptSummary, string $responseSummary, int $tokensUsed = 0): void { Database::insert('ai_interactions', [ 'organization_id' => $orgId, 'user_id' => $userId, 'interaction_type' => $type, 'prompt_summary' => substr($promptSummary, 0, 500), 'response_summary' => $responseSummary, 'tokens_used' => $tokensUsed, 'model_used' => $this->model, ]); } }