[FIX] Gap Analysis ACN: bug da review avversariale (4 agenti)
#1 CRITICO aiAnalyze: askWithRag ritorna ['answer','sources','rag_used'], non una stringa. Ora estrae 'answer' (ai_summary) e salva 'sources' in ai_recommendations. Prima salvava il JSON intero in ai_summary. #2 ALTO corpus RAG: acn_requirements.json aveva 188/203 testi TRONCATI alla prima riga PDF (es. GV.PO-01#1: 84 char invece di 838). Rigenerato dai testi INTEGRALI di acn_measures.json (87+116, zero troncamenti). Ri-ingest Qdrant. #3 MEDIO catalog(): org non classificata dava entity_level=null + warning PHP $totals[null] + TypeError frontend. Ora 422 ENTITY_LEVEL_REQUIRED come create(). #4 MEDIO guida cap-5 GV.RR-04: "figure chiave dell'organigramma" era errato e auto-contraddittorio -> "personale autorizzato + amministratori di sistema, valutazione esperienza/capacita/affidabilita" (allineato testo ACN). #5 BASSI: openAcn try/catch (no unhandled rejection su Riprendi); badge importante/essenziale IT/EN; overall_score=null (non 0.0) se tutti N/A. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
09f51795c1
commit
d6924a30c1
@ -47,6 +47,15 @@ class AcnAssessmentController extends BaseController
|
|||||||
{
|
{
|
||||||
$this->requireOrgAccess();
|
$this->requireOrgAccess();
|
||||||
$level = $this->resolveEntityLevel();
|
$level = $this->resolveEntityLevel();
|
||||||
|
if ($level === null) {
|
||||||
|
// Coerente con create(): senza classificazione non c'e perimetro.
|
||||||
|
// Il frontend intercetta ENTITY_LEVEL_REQUIRED e mostra l'invito a classificare.
|
||||||
|
$this->jsonError(
|
||||||
|
'Il soggetto non e classificato come importante o essenziale. Completa prima la classificazione NIS2 dell\'organizzazione.',
|
||||||
|
422,
|
||||||
|
'ENTITY_LEVEL_REQUIRED'
|
||||||
|
);
|
||||||
|
}
|
||||||
$grouped = $this->groupedRequirements($level, []);
|
$grouped = $this->groupedRequirements($level, []);
|
||||||
|
|
||||||
$totals = $this->datasetTotals();
|
$totals = $this->datasetTotals();
|
||||||
@ -385,18 +394,23 @@ class AcnAssessmentController extends BaseController
|
|||||||
'organization_id' => $this->getCurrentOrgId(),
|
'organization_id' => $this->getCurrentOrgId(),
|
||||||
'consulting_firm_id' => $this->currentUser['consulting_firm_id'] ?? null,
|
'consulting_firm_id' => $this->currentUser['consulting_firm_id'] ?? null,
|
||||||
];
|
];
|
||||||
$answer = $ai->askWithRag($question, $userContext);
|
// askWithRag ritorna ['answer'=>string, 'sources'=>array, 'rag_used'=>bool]:
|
||||||
|
// estraiamo il testo, NON salviamo l'intero array.
|
||||||
|
$result = $ai->askWithRag($question, $userContext);
|
||||||
|
$answer = is_array($result) ? (string) ($result['answer'] ?? '') : (string) $result;
|
||||||
|
$sources = (is_array($result) && isset($result['sources'])) ? $result['sources'] : [];
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
error_log('[AcnAssessment] aiAnalyze fallita: ' . $e->getMessage());
|
error_log('[AcnAssessment] aiAnalyze fallita: ' . $e->getMessage());
|
||||||
$this->jsonError('Analisi AI temporaneamente non disponibile. Riprova piu tardi.', 503, 'AI_UNAVAILABLE');
|
$this->jsonError('Analisi AI temporaneamente non disponibile. Riprova piu tardi.', 503, 'AI_UNAVAILABLE');
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::update('acn_assessments', [
|
Database::update('acn_assessments', [
|
||||||
'ai_summary' => is_string($answer) ? $answer : json_encode($answer, JSON_UNESCAPED_UNICODE),
|
'ai_summary' => $answer,
|
||||||
|
'ai_recommendations' => json_encode($sources, JSON_UNESCAPED_UNICODE),
|
||||||
], 'id = ?', [$id]);
|
], 'id = ?', [$id]);
|
||||||
|
|
||||||
$this->logAudit('acn_assessment_ai_analyzed', 'acn_assessment', $id, ['gaps' => count($gaps)]);
|
$this->logAudit('acn_assessment_ai_analyzed', 'acn_assessment', $id, ['gaps' => count($gaps)]);
|
||||||
$this->jsonSuccess(['ai_summary' => $answer], 'Analisi AI completata');
|
$this->jsonSuccess(['ai_summary' => $answer, 'sources' => $sources], 'Analisi AI completata');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ════════════════════════ HELPER ════════════════════════
|
// ════════════════════════ HELPER ════════════════════════
|
||||||
@ -580,7 +594,9 @@ class AcnAssessmentController extends BaseController
|
|||||||
$cnt++;
|
$cnt++;
|
||||||
}
|
}
|
||||||
|
|
||||||
$overall = $cnt > 0 ? round($sum / $cnt, 2) : 0.0;
|
// Se nessun requisito e applicabile (tutti not_applicable) il punteggio
|
||||||
|
// non e definito: null, non 0.0 (che significherebbe "0% conforme").
|
||||||
|
$overall = $cnt > 0 ? round($sum / $cnt, 2) : null;
|
||||||
$funcScores = [];
|
$funcScores = [];
|
||||||
foreach ($byFunc as $fc => $d) {
|
foreach ($byFunc as $fc => $d) {
|
||||||
$funcScores[$fc] = $d['cnt'] > 0 ? round($d['sum'] / $d['cnt'], 2) : 0.0;
|
$funcScores[$fc] = $d['cnt'] > 0 ? round($d['sum'] / $d['cnt'], 2) : 0.0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -158,8 +158,8 @@
|
|||||||
const cat = await api.acnCatalog();
|
const cat = await api.acnCatalog();
|
||||||
ACN.level = cat.entity_level;
|
ACN.level = cat.entity_level;
|
||||||
const lvlTxt = cat.entity_level === 'essential'
|
const lvlTxt = cat.entity_level === 'essential'
|
||||||
? '<span class="acn-level-badge acn-level-essential">Soggetto ESSENZIALE</span>'
|
? '<span class="acn-level-badge acn-level-essential">'+(lang()==='en'?'ESSENTIAL entity':'Soggetto ESSENZIALE')+'</span>'
|
||||||
: '<span class="acn-level-badge acn-level-important">Soggetto IMPORTANTE</span>';
|
: '<span class="acn-level-badge acn-level-important">'+(lang()==='en'?'IMPORTANT entity':'Soggetto IMPORTANTE')+'</span>';
|
||||||
el('acn-level-info').innerHTML = lvlTxt +
|
el('acn-level-info').innerHTML = lvlTxt +
|
||||||
'<p class="text-muted" style="margin-top:10px;">' +
|
'<p class="text-muted" style="margin-top:10px;">' +
|
||||||
(lang()==='en'?'Applicable scope: ':'Perimetro applicabile: ') +
|
(lang()==='en'?'Applicable scope: ':'Perimetro applicabile: ') +
|
||||||
@ -228,6 +228,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openAcn(id, status){
|
async function openAcn(id, status){
|
||||||
|
try {
|
||||||
ACN.id = id;
|
ACN.id = id;
|
||||||
const res = await api.acnRequirements(id);
|
const res = await api.acnRequirements(id);
|
||||||
ACN.level = res.assessment.entity_level;
|
ACN.level = res.assessment.entity_level;
|
||||||
@ -236,6 +237,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderWizard(res);
|
renderWizard(res);
|
||||||
|
} catch(e){
|
||||||
|
alert((e && e.message) || (lang()==='en'?'Could not open the assessment.':'Impossibile aprire l\'assessment.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderWizard(res){
|
function renderWizard(res){
|
||||||
@ -245,8 +249,8 @@
|
|||||||
el('acn-wizard').style.display='block';
|
el('acn-wizard').style.display='block';
|
||||||
el('btn-ai-analyze').style.display='none';
|
el('btn-ai-analyze').style.display='none';
|
||||||
el('acn-wizard-level').innerHTML = ACN.level==='essential'
|
el('acn-wizard-level').innerHTML = ACN.level==='essential'
|
||||||
? '<span class="acn-level-badge acn-level-essential">ESSENZIALE</span>'
|
? '<span class="acn-level-badge acn-level-essential">'+(lang()==='en'?'ESSENTIAL':'ESSENZIALE')+'</span>'
|
||||||
: '<span class="acn-level-badge acn-level-important">IMPORTANTE</span>';
|
: '<span class="acn-level-badge acn-level-important">'+(lang()==='en'?'IMPORTANT':'IMPORTANTE')+'</span>';
|
||||||
|
|
||||||
ACN.answers = {}; ACN.total = 0;
|
ACN.answers = {}; ACN.total = 0;
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|||||||
@ -458,9 +458,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="example-box">
|
<div class="example-box">
|
||||||
<strong>Esempio (requisiti di governance).</strong> <code>GV.RR-04</code> richiede che la cybersicurezza
|
<strong>Esempio (requisiti di governance).</strong> <code>GV.RR-04</code> richiede che la cybersicurezza
|
||||||
sia integrata nella gestione delle risorse umane: chi accede ai sistemi (utenti e amministratori) deve
|
sia integrata nella gestione delle risorse umane: il <strong>personale autorizzato ad accedere</strong>
|
||||||
avere <strong>competenze e affidabilità adeguate al ruolo</strong>, valutate e documentate — è il
|
ai sistemi rilevanti e gli <strong>amministratori di sistema</strong> devono essere individuati previa
|
||||||
"controllo di adeguatezza al ruolo" delle figure chiave dell'organigramma.
|
valutazione di <strong>esperienza, capacità e affidabilità</strong> (non necessariamente una certificazione),
|
||||||
|
e tale valutazione va documentata.
|
||||||
<code>GV.PO-01</code> richiede una <strong>policy di gestione del rischio cyber</strong> formalizzata,
|
<code>GV.PO-01</code> richiede una <strong>policy di gestione del rischio cyber</strong> formalizzata,
|
||||||
comunicata e applicata.
|
comunicata e applicata.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user