[FEAT] Knowledge Base RAG multi-livello (SYSTEM/FIRM/ORG) + Qdrant + Voyage

- KnowledgeBaseController: ingest, list, firmOrgs, search, delete
- VectorService (Qdrant + buildAuthzFilter), EmbedService (Voyage), RagService (pipeline)
- AIService::askWithRag con fallback graceful
- docker-compose: servizio qdrant + env Voyage (chiave da .env/vault, no hardcoded)
- SQL 012 consulting_firms, 013 firm_assignments + kb_uploaded_documents
- public/kb.html + kb.js (upload, lista, search preview)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-05-29 15:44:13 +02:00
co-authored by Claude Opus 4.8
parent 9b53ca3ba1
commit a7a21faa82
10 changed files with 1164 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
-- NIS2 Migration 012: Consulting Firms (Studio di Consulenza)
-- Database: nis2_agile_db
-- Data: 2026-04-11
-- Idempotente: rieseguibile in sicurezza.
USE nis2_agile_db;
-- 1. Anagrafica studi di consulenza cybersecurity / NIS2 (idempotente)
CREATE TABLE IF NOT EXISTS consulting_firms (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL COMMENT 'Ragione sociale studio',
vat_number VARCHAR(20) NULL,
fiscal_code VARCHAR(16) NULL,
forma_giuridica VARCHAR(50) NULL,
address VARCHAR(255) NULL,
city VARCHAR(100) NULL,
province VARCHAR(2) NULL,
cap VARCHAR(5) NULL,
phone VARCHAR(30) NULL,
pec VARCHAR(255) NULL,
website VARCHAR(255) NULL,
plan ENUM('starter','professional','enterprise') NOT NULL DEFAULT 'professional',
max_organizations INT NOT NULL DEFAULT 50,
max_users INT NOT NULL DEFAULT 5,
status ENUM('active','trial','suspended','inactive') NOT NULL DEFAULT 'active',
created_by INT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_vat (vat_number),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 2. ALTER users.consulting_firm_id (idempotente)
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'consulting_firm_id');
SET @sql := IF(@col = 0,
'ALTER TABLE users ADD COLUMN consulting_firm_id INT NULL AFTER role',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx := (SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND INDEX_NAME = 'idx_user_firm');
SET @sql := IF(@idx = 0,
'ALTER TABLE users ADD INDEX idx_user_firm (consulting_firm_id)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 3. ALTER organizations.consulting_firm_id (idempotente)
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'organizations' AND COLUMN_NAME = 'consulting_firm_id');
SET @sql := IF(@col = 0,
'ALTER TABLE organizations ADD COLUMN consulting_firm_id INT NULL',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx := (SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'organizations' AND INDEX_NAME = 'idx_org_firm');
SET @sql := IF(@idx = 0,
'ALTER TABLE organizations ADD INDEX idx_org_firm (consulting_firm_id)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+41
View File
@@ -0,0 +1,41 @@
-- NIS2 Migration 013: Firm-Organization assignments + KB tracking
-- Database: nis2_agile_db
-- Data: 2026-04-11
USE nis2_agile_db;
-- 1. Mapping firm -> organization (M:N)
CREATE TABLE IF NOT EXISTS firm_org_assignments (
id INT AUTO_INCREMENT PRIMARY KEY,
consulting_firm_id INT NOT NULL,
organization_id INT NOT NULL,
assigned_to INT NOT NULL DEFAULT 0
COMMENT '0=tutti i membri del firm, user_id=membro specifico',
assigned_by INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_firm_org_user (consulting_firm_id, organization_id, assigned_to),
INDEX idx_firm (consulting_firm_id),
INDEX idx_org (organization_id),
INDEX idx_assigned (assigned_to)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 2. Registro documenti uploadati nella KB (audit + listing)
CREATE TABLE IF NOT EXISTS kb_uploaded_documents (
id INT AUTO_INCREMENT PRIMARY KEY,
qdrant_doc_uuid VARCHAR(64) NOT NULL COMMENT 'Identifica il gruppo di chunk in Qdrant (per delete-by-filter)',
scope ENUM('SYSTEM','FIRM','ORG') NOT NULL DEFAULT 'SYSTEM',
consulting_firm_id INT NULL,
organization_id INT NULL,
uploaded_by INT NOT NULL,
title VARCHAR(255) NOT NULL,
entity_type VARCHAR(64) NULL,
source VARCHAR(255) NULL,
lang VARCHAR(5) NOT NULL DEFAULT 'it',
chunk_count INT NOT NULL DEFAULT 0,
shared_with_orgs JSON NULL COMMENT 'Array di organization_id quando scope=FIRM',
status ENUM('processing','ready','failed') NOT NULL DEFAULT 'ready',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_scope_firm (scope, consulting_firm_id),
INDEX idx_scope_org (scope, organization_id),
INDEX idx_uuid (qdrant_doc_uuid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;