[KB] #384 — ingest KB/RAG 'Archivio Policy unificato' (scope SYSTEM)
Indicizza in Qdrant nis2_kb (SYSTEM) la conoscenza prodotto sulle nuove funzioni Policy: unificazione Policy/Procedura, collegamento M:N con Misure e Requisiti, allegati, matrice RACI (con la nota fonti-certe: R+A per approvare = regola di prodotto, non obbligo NIS2; ISO distingue policy/procedura come best practice). Script self-contained sotto application/cli (montata in nis2-app), doc inline, idempotente per source. Eseguito host-side: 2 chunk, doc_uuid 1df7926a, tracking kb_uploaded_documents id=17. Verifica RAG: searchForUser ritorna il doc come 1° risultato (score 0.63-0.67) su 3 query (Policy=Procedura, RACI, allegati/link). Chiude il residuo KB di #384. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
726a2a80ef
commit
fb2b7ecc2f
@@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Ingest KB (RAG) — Archivio Policy unificato (ticket #384).
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* Indicizza nella collection Qdrant `nis2_kb` (scope SYSTEM) la conoscenza
|
||||||
|
* prodotto sulle nuove funzioni Policy (#384): unificazione Policy/Procedura,
|
||||||
|
* collegamento M:N con Misure e Requisiti, allegati, matrice RACI. Cosi'
|
||||||
|
* l'assistente AI (ARIA) e AIService::askWithRag() possono rispondere e citare.
|
||||||
|
*
|
||||||
|
* Self-contained: il testo del documento e' INLINE (niente file esterno), e lo
|
||||||
|
* script vive sotto application/cli/ (montata in nis2-app) → si esegue diretto:
|
||||||
|
* docker exec nis2-app php /var/www/nis2-agile/application/cli/ingest_kb_policy_384.php
|
||||||
|
* docker exec nis2-app php .../ingest_kb_policy_384.php --dry-run # statistiche, no upsert
|
||||||
|
*
|
||||||
|
* Idempotente: cancella i chunk SYSTEM di questa stessa `source` prima del re-upsert.
|
||||||
|
* Richiede Qdrant (172.21.0.5) + Voyage → eseguire SU HETZNER (nis2-app), non dal devenv.
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
if (PHP_SAPI !== 'cli') { fwrite(STDERR, "Solo CLI\n"); exit(1); }
|
||||||
|
|
||||||
|
define('APP_PATH', dirname(__DIR__)); // .../application
|
||||||
|
define('BASE_PATH', dirname(APP_PATH)); // project root
|
||||||
|
require_once APP_PATH . '/config/env.php';
|
||||||
|
require_once APP_PATH . '/config/config.php';
|
||||||
|
require_once APP_PATH . '/config/database.php';
|
||||||
|
require_once APP_PATH . '/services/EmbedService.php';
|
||||||
|
require_once APP_PATH . '/services/VectorService.php';
|
||||||
|
|
||||||
|
$opts = getopt('', ['dry-run']);
|
||||||
|
$dryRun = isset($opts['dry-run']);
|
||||||
|
|
||||||
|
$SOURCE = 'NIS2 Agile — Archivio Policy unificato (#384)';
|
||||||
|
$TITLE = 'Policy unificate: collegamento Misure e Requisiti, allegati, matrice RACI';
|
||||||
|
|
||||||
|
$DOC = <<<'MD'
|
||||||
|
DOCUMENTO: Archivio Policy unificato in NIS2 Agile (funzioni introdotte dal ticket #384).
|
||||||
|
USO: aiutare l'utente a capire le Policy, il loro collegamento con Misure e Requisiti, gli allegati e la matrice RACI.
|
||||||
|
|
||||||
|
## Policy e Procedure: un unico archivio
|
||||||
|
In NIS2 Agile i termini "Policy" e "Procedura" indicano lo stesso oggetto: l'archivio e' unico ed e' gestito dalla pagina "Policy". Una policy puo' rappresentare sia una politica di alto livello sia una procedura operativa.
|
||||||
|
Nota di metodo (fonti certe): questa unificazione e' una scelta di prodotto. Nessuna norma NIS2 — Direttiva (UE) 2022/2555 e D.Lgs. 138/2024 art. 24 (misure di gestione del rischio) — impone di tenere separate policy e procedure. Lo standard ISO/IEC 27001:2022 (cl. 5.2) e la ISO/IEC 27002 distinguono la "policy" (dichiarazione di intenti di alto livello) dalla "procedura" (documento operativo): e' una buona pratica, NON un obbligo di legge.
|
||||||
|
|
||||||
|
## Codici Policy
|
||||||
|
Ogni policy ha un codice nella forma "Policy.NN" (in precedenza "Proc.NN"). Il codice identifica la policy nell'elenco e nei collegamenti.
|
||||||
|
|
||||||
|
## Collegamento con Misure e Requisiti (relazione molti-a-molti)
|
||||||
|
Le pagine "Policy" e "Misure e Requisiti" sono collegate con una relazione molti-a-molti:
|
||||||
|
- un requisito NIS2 puo' essere coperto da piu' policy;
|
||||||
|
- una policy puo' coprire piu' requisiti NIS2.
|
||||||
|
Nella pagina "Misure e Requisiti" la colonna "Policy (default)" elenca tutte le policy collegate a ciascun requisito. Nella pagina "Policy" la colonna "Requisito NIS2" elenca tutti i requisiti collegati a ciascuna policy; il codice del requisito e' la concatenazione di MISURA + REQUISITO (esempio: "DE.CM-01 comma 1").
|
||||||
|
I codici mostrati sono link cliccabili: da una policy si apre il requisito collegato, e dal requisito si apre la policy collegata (navigazione bidirezionale tra le due pagine).
|
||||||
|
|
||||||
|
## Allegati alle Policy
|
||||||
|
Nel dettaglio di ogni policy e' presente la sezione "Allegati": si possono caricare file (PDF, immagini PNG/JPG/GIF/WEBP, documenti Office DOC/DOCX/XLS/XLSX/PPT/PPTX/ODT/ODS, txt, csv, zip), fino a 10 MB ciascuno. Per sicurezza non sono ammessi file html, svg o js (prevenzione XSS). Ogni allegato e' registrato con autore e data e puo' essere scaricato.
|
||||||
|
|
||||||
|
## Matrice RACI sulla Policy
|
||||||
|
Ogni policy ha una "Matrice RACI" per assegnare le responsabilita' ai ruoli definiti nell'Organigramma del prodotto. I quattro ruoli RACI sono:
|
||||||
|
- R = Responsible: chi esegue concretamente l'attivita';
|
||||||
|
- A = Accountable: chi ne risponde e la approva (unico responsabile finale);
|
||||||
|
- C = Consulted: chi viene consultato;
|
||||||
|
- I = Informed: chi viene informato.
|
||||||
|
Regola di approvazione: per APPROVARE una policy occorre aver assegnato almeno una R e almeno una A; in stato di bozza l'assegnazione e' libera e non vincolante. Questa e' una regola di governance interna del prodotto, NON un obbligo normativo NIS2: serve a garantire che ogni policy approvata abbia un esecutore e un responsabile chiari.
|
||||||
|
|
||||||
|
## Finestra di modifica Policy
|
||||||
|
Nella finestra "Modifica Policy" le etichette dei campi compaiono sopra i rispettivi campi (allineamento corretto, conforme al design system AGID/Bootstrap Italia).
|
||||||
|
MD;
|
||||||
|
|
||||||
|
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
|
||||||
|
|
||||||
|
function chunkText(string $text, int $size = 2000, int $overlap = 200): array
|
||||||
|
{
|
||||||
|
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
|
||||||
|
$chunks = []; $len = mb_strlen($text, 'UTF-8'); $start = 0;
|
||||||
|
while ($start < $len) {
|
||||||
|
$take = min($size, $len - $start);
|
||||||
|
$piece = mb_substr($text, $start, $take, 'UTF-8');
|
||||||
|
if (trim($piece) !== '') $chunks[] = $piece;
|
||||||
|
if ($start + $take >= $len) break;
|
||||||
|
$start += ($size - $overlap);
|
||||||
|
}
|
||||||
|
return $chunks;
|
||||||
|
}
|
||||||
|
function uuid(): string
|
||||||
|
{
|
||||||
|
$b = random_bytes(16);
|
||||||
|
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
|
||||||
|
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
|
||||||
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
logln('=== Ingest KB "Archivio Policy unificato (#384)" (scope SYSTEM) ===');
|
||||||
|
if ($dryRun) logln('MODALITA DRY-RUN: nessun upsert.');
|
||||||
|
|
||||||
|
$text = preg_replace('/[ \t]+/', ' ', $DOC);
|
||||||
|
$text = preg_replace('/\n{3,}/', "\n\n", trim($text));
|
||||||
|
if (strlen($text) < 200) { logln('ERRORE: testo troppo breve.'); exit(1); }
|
||||||
|
|
||||||
|
$header = "DOCUMENTO: Funzioni Policy di NIS2 Agile (unificazione Policy/Procedura, collegamento Misure e Requisiti, allegati, matrice RACI).\n"
|
||||||
|
. "USO: rispondere alle domande dell'utente su Policy, requisiti collegati, allegati e RACI.\n\n";
|
||||||
|
$chunks = chunkText($header . $text, 2000, 200);
|
||||||
|
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
|
||||||
|
if ($dryRun) { logln('=== DRY-RUN completato ==='); exit(0); }
|
||||||
|
|
||||||
|
$embed = new EmbedService();
|
||||||
|
$vector = new VectorService();
|
||||||
|
$vector->ensureCollection($embed->dims);
|
||||||
|
|
||||||
|
// Idempotenza: rimuovi i chunk SYSTEM esistenti per questa fonte
|
||||||
|
try {
|
||||||
|
$vector->deleteByFilter(['must' => [
|
||||||
|
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
|
||||||
|
['key' => 'source', 'match' => ['value' => $SOURCE]],
|
||||||
|
]]);
|
||||||
|
} catch (Exception $e) { logln(' (warning) delete precedente: ' . $e->getMessage()); }
|
||||||
|
|
||||||
|
$docUuid = uuid();
|
||||||
|
$points = [];
|
||||||
|
foreach ($chunks as $i => $chunk) {
|
||||||
|
$vec = null;
|
||||||
|
for ($try = 1; $try <= 5; $try++) {
|
||||||
|
try { $vec = $embed->embed($chunk); break; }
|
||||||
|
catch (Throwable $e) {
|
||||||
|
if ($try === 5) { logln(" ERRORE embed chunk {$i} dopo 5 tentativi: " . $e->getMessage()); throw $e; }
|
||||||
|
logln(" retry embed chunk {$i} (tentativo {$try})"); sleep($try);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$points[] = [
|
||||||
|
'id' => uuid(),
|
||||||
|
'vector' => $vec,
|
||||||
|
'payload' => [
|
||||||
|
'doc_uuid' => $docUuid,
|
||||||
|
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
|
||||||
|
'chunk' => $chunk,
|
||||||
|
'entity_type' => 'guida_prodotto',
|
||||||
|
'source' => $SOURCE,
|
||||||
|
'lang' => 'it',
|
||||||
|
'scope' => 'SYSTEM',
|
||||||
|
'consulting_firm_id' => null,
|
||||||
|
'organization_id' => null,
|
||||||
|
'shared_with_orgs' => [],
|
||||||
|
'uploaded_by' => 0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
foreach (array_chunk($points, 64) as $batch) { $vector->upsertBatch($batch); }
|
||||||
|
|
||||||
|
// Tracking MySQL (best-effort)
|
||||||
|
try {
|
||||||
|
$stmt = Database::getInstance()->prepare(
|
||||||
|
"INSERT INTO kb_uploaded_documents
|
||||||
|
(qdrant_doc_uuid, scope, consulting_firm_id, organization_id, uploaded_by, title, entity_type, source, lang, chunk_count, shared_with_orgs, status)
|
||||||
|
VALUES (?, 'SYSTEM', NULL, NULL, 0, ?, 'guida_prodotto', ?, 'it', ?, '[]', 'ready')"
|
||||||
|
);
|
||||||
|
$stmt->execute([$docUuid, $TITLE, $SOURCE, count($chunks)]);
|
||||||
|
} catch (Exception $e) { logln(' (warning) tracking insert: ' . $e->getMessage()); }
|
||||||
|
|
||||||
|
logln("=== OK indicizzato: doc_uuid={$docUuid}, " . count($chunks) . " chunk ===");
|
||||||
Reference in New Issue
Block a user