From fb2b7ecc2f0a6de559dd11da59b657d1a51f1d63 Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Fri, 19 Jun 2026 07:06:39 +0200 Subject: [PATCH] =?UTF-8?q?[KB]=20#384=20=E2=80=94=20ingest=20KB/RAG=20'Ar?= =?UTF-8?q?chivio=20Policy=20unificato'=20(scope=20SYSTEM)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- application/cli/ingest_kb_policy_384.php | 157 +++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 application/cli/ingest_kb_policy_384.php diff --git a/application/cli/ingest_kb_policy_384.php b/application/cli/ingest_kb_policy_384.php new file mode 100644 index 0000000..c4893f8 --- /dev/null +++ b/application/cli/ingest_kb_policy_384.php @@ -0,0 +1,157 @@ += $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 ===");