[FEAT] L4 AI Cross-Analysis — analisi aggregata multi-org per consulenti
- CrossAnalysisController.php: analyze/history/portfolio (k-anonymity min 2 org)
- AIService::crossOrgAnalysis(): aggregazione 9 dimensioni, zero PII nel prompt
- cross-analysis.html: chat UI purple theme, 3 tab, quick questions, portfolio stats
- index.php: routing /api/cross-analysis/{analyze,history,portfolio}
- common.js: link "AI Cross-Analysis" in sidebar sezione Gestione
- docs/AI_LEVELS_SCHEMA.md: schema architetturale L1-L5 con matrice privacy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
89fd201bc2
commit
19a9e5622d
@@ -310,6 +310,154 @@ PROMPT;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analisi cross-organizzazione per consulenti (L4)
|
||||
* Riceve dati già aggregati e anonimizzati dal controller.
|
||||
* NON deve mai ricevere nomi org, P.IVA o dati identificativi.
|
||||
*/
|
||||
public function crossOrgAnalysis(int $orgCount, array $aggregated, string $question): array
|
||||
{
|
||||
$context = $this->buildCrossOrgContext($aggregated, $orgCount);
|
||||
|
||||
$system = <<<SYSTEM
|
||||
Sei un analista di cybersecurity e compliance NIS2 specializzato nell'analisi comparativa multi-organizzazione.
|
||||
Rispondi sempre in italiano, in modo professionale e sintetico.
|
||||
|
||||
REGOLA FONDAMENTALE DI PRIVACY (non derogabile):
|
||||
- Non fare mai riferimento a organizzazioni specifiche o identificabili.
|
||||
- Rispondi SOLO con statistiche aggregate e trend generali.
|
||||
- Se una risposta richiederebbe identificare una singola organizzazione, rifiuta con: "Dato non disponibile per protezione della privacy".
|
||||
- Non inventare dati non presenti nel contesto.
|
||||
SYSTEM;
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
## Portfolio analizzato: {$orgCount} organizzazioni (dati aggregati e anonimizzati)
|
||||
|
||||
{$context}
|
||||
|
||||
## Domanda del consulente
|
||||
{$question}
|
||||
|
||||
Rispondi in formato JSON:
|
||||
{
|
||||
"answer": "Risposta principale dettagliata (3-6 paragrafi)",
|
||||
"key_findings": ["Risultato chiave 1", "Risultato chiave 2"],
|
||||
"recommendations": ["Raccomandazione pratica 1", "Raccomandazione pratica 2"],
|
||||
"risk_areas": ["Area critica 1", "Area critica 2"],
|
||||
"benchmark_note": "Nota comparativa settoriale se rilevante, altrimenti null",
|
||||
"privacy_note": null
|
||||
}
|
||||
|
||||
Rispondi SOLO con il JSON.
|
||||
PROMPT;
|
||||
|
||||
$response = $this->callAPI($prompt, $system);
|
||||
return $this->parseJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Costruisce il contesto aggregato per il prompt cross-org
|
||||
*/
|
||||
private function buildCrossOrgContext(array $d, int $orgCount): string
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
// Distribuzione settoriale
|
||||
if (!empty($d['by_sector'])) {
|
||||
$lines[] = '### Distribuzione Settoriale';
|
||||
foreach ($d['by_sector'] as $sector => $count) {
|
||||
$pct = round($count / $orgCount * 100);
|
||||
$lines[] = "- {$sector}: {$count} org ({$pct}%)";
|
||||
}
|
||||
}
|
||||
|
||||
// Classificazione NIS2
|
||||
if (!empty($d['by_entity_type'])) {
|
||||
$lines[] = "\n### Classificazione NIS2";
|
||||
foreach ($d['by_entity_type'] as $type => $count) {
|
||||
$lines[] = "- {$type}: {$count} org";
|
||||
}
|
||||
}
|
||||
|
||||
// Compliance score
|
||||
if (isset($d['avg_compliance_score'])) {
|
||||
$lines[] = "\n### Compliance Score Medio: {$d['avg_compliance_score']}%";
|
||||
if (isset($d['score_distribution'])) {
|
||||
foreach ($d['score_distribution'] as $range => $count) {
|
||||
$lines[] = "- {$range}: {$count} org";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assessment
|
||||
if (isset($d['assessments'])) {
|
||||
$a = $d['assessments'];
|
||||
$lines[] = "\n### Gap Assessment";
|
||||
$lines[] = "- Org con assessment completato: {$a['with_completed']} / {$orgCount}";
|
||||
if (!empty($a['avg_by_category'])) {
|
||||
$lines[] = "- Score medio per categoria:";
|
||||
foreach ($a['avg_by_category'] as $cat => $score) {
|
||||
$lines[] = " - {$cat}: {$score}%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rischi
|
||||
if (isset($d['risks'])) {
|
||||
$r = $d['risks'];
|
||||
$lines[] = "\n### Rischi (aggregato)";
|
||||
$lines[] = "- Totale rischi aperti: {$r['total_open']}";
|
||||
$lines[] = "- Media rischi per org: {$r['avg_per_org']}";
|
||||
if (!empty($r['by_severity'])) {
|
||||
foreach ($r['by_severity'] as $sev => $count) {
|
||||
$lines[] = "- Gravità {$sev}: {$count} rischi";
|
||||
}
|
||||
}
|
||||
$lines[] = "- % con piano trattamento: {$r['pct_with_treatment']}%";
|
||||
}
|
||||
|
||||
// Policy
|
||||
if (isset($d['policies'])) {
|
||||
$p = $d['policies'];
|
||||
$lines[] = "\n### Policy";
|
||||
$lines[] = "- Media policy approvate per org: {$p['avg_approved']}";
|
||||
$lines[] = "- Org senza policy approvate: {$p['without_approved']}";
|
||||
}
|
||||
|
||||
// Formazione
|
||||
if (isset($d['training'])) {
|
||||
$t = $d['training'];
|
||||
$lines[] = "\n### Formazione";
|
||||
$lines[] = "- Tasso completamento medio: {$t['avg_completion_rate']}%";
|
||||
$lines[] = "- Org con completamento < 50%: {$t['below_50pct']}";
|
||||
}
|
||||
|
||||
// Controlli
|
||||
if (isset($d['controls'])) {
|
||||
$c = $d['controls'];
|
||||
$lines[] = "\n### Controlli ISO 27001 / NIS2";
|
||||
$lines[] = "- % media implementazione: {$c['avg_implementation']}%";
|
||||
if (!empty($c['weakest_categories'])) {
|
||||
$lines[] = "- Categorie più deboli: " . implode(', ', $c['weakest_categories']);
|
||||
}
|
||||
}
|
||||
|
||||
// Incidenti
|
||||
if (isset($d['incidents'])) {
|
||||
$i = $d['incidents'];
|
||||
$lines[] = "\n### Incidenti (ultimi 12 mesi)";
|
||||
$lines[] = "- Totale incidenti: {$i['total']}";
|
||||
$lines[] = "- Org con almeno 1 incidente: {$i['orgs_with_incidents']}";
|
||||
if (!empty($i['by_severity'])) {
|
||||
foreach ($i['by_severity'] as $sev => $count) {
|
||||
$lines[] = "- Gravità {$sev}: {$count}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra interazione AI nel database
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user