[FEAT] Sistema Segnalazioni & Risoluzione AI (feedback)

Adattato da alltax.it — il sistema più maturo testato con utenti reali.

Backend:
- FeedbackController: 6 endpoint (submit, mine, list, show, update, resolve)
- FeedbackService: createReport + classifyWithAI + broadcastResolution
- AIService::classifyFeedback() — 10s timeout, 500 token, JSON puro
- EmailService::sendFeedbackResolved() — broadcast email org
- DB migration 014: tabella feedback_reports

Frontend:
- feedback.js: FAB rosso #EF4444, modal 2 fasi (form → AI → password gate)
- Tab "Le mie segnalazioni" con badge status
- Auto-init su tutte le pagine autenticate (common.js::checkAuth)
- api.js: 6 metodi client; style.css: stili completi

Worker:
- scripts/feedback-worker.php: cron ogni 30 min
  → docker exec nis2-agile-devenv + Claude Code CLI
  → risoluzione autonoma con POST /api/feedback/{id}/resolve

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-03-10 08:51:52 +01:00
co-authored by Claude Sonnet 4.6
parent 3b51b5bd95
commit 1382530189
13 changed files with 1911 additions and 43 deletions
+71
View File
@@ -310,6 +310,77 @@ PROMPT;
return $data;
}
/**
* Classifica una segnalazione feedback/bug per il sistema di ticketing interno
*
* @param string $tipo Tipo segnalazione (bug|ux|funzionalita|domanda|altro)
* @param string $descrizione Testo della segnalazione (non contiene PII org)
* @return array ['categoria', 'priorita', 'suggerimento', 'risposta_utente']
*/
public function classifyFeedback(string $tipo, string $descrizione): array
{
$prompt = <<<PROMPT
Sei un assistente tecnico per una piattaforma SaaS di compliance NIS2.
Analizza la seguente segnalazione e rispondi SOLO con un oggetto JSON valido.
Tipo segnalazione: {$tipo}
Descrizione: {$descrizione}
Rispondi con questo JSON (senza markdown, solo JSON puro):
{
"categoria": "una tra: autenticazione, dashboard, rischi, incidenti, policy, assessment, formazione, fornitori, asset, report, impostazioni, performance, interfaccia, documentazione, altro",
"priorita": "una tra: alta, media, bassa",
"suggerimento": "suggerimento tecnico breve per il team di sviluppo (max 150 caratteri)",
"risposta_utente": "risposta leggibile e rassicurante per l'utente finale in italiano (max 200 caratteri)"
}
Criteri priorità:
- alta: blocca funzionalità critica (compliance, accesso, salvataggio dati)
- media: degrada l'esperienza ma esiste un workaround
- bassa: miglioria estetica o funzionalità secondaria
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([
'model' => $this->model,
'max_tokens' => 500,
'system' => 'Sei un assistente tecnico. Rispondi SEMPRE con JSON puro, senza markdown né testo aggiuntivo.',
'messages' => [['role' => 'user', 'content' => $prompt]],
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError || $httpCode !== 200) {
throw new RuntimeException("classifyFeedback AI error [{$httpCode}]: {$curlError}");
}
$data = json_decode($response, true);
$text = $data['content'][0]['text'] ?? '';
$result = $this->parseJsonResponse($text);
// Normalizza priorità
$validP = ['alta', 'media', 'bassa'];
if (!in_array($result['priorita'] ?? '', $validP)) {
$result['priorita'] = 'media';
}
return $result;
}
/**
* Analisi cross-organizzazione per consulenti (L4)
* Riceve dati già aggregati e anonimizzati dal controller.