nis2-agile/docs/sql/026_risk_quantitative.sql
DevEnv nis2-agile 1be3bd01a4 [FEAT] Risk quantitativo FAIR + KRI dashboard (P2)
Competizione coi GRC enterprise sul risk management quantitativo:
- FairService: simulazione Monte Carlo FAIR (PERT su TEF e Loss Magnitude),
  ALE in EUR con percentili P10/P50/P90 + istogramma, deterministico (seed da input)
- RiskController::computeFair -> POST /risks/{id}/fair (persiste parametri+ALE)
- RiskController::fairRegister -> GET /risks/fairRegister (portfolio ALE EUR)
- KRI: listKri/createKri/updateKri (GET/POST /risks/kri, PUT /risks/kri/{id})
  con stato semaforo green/amber/red su soglie+direzione
- Migrazione 026: risks += parametri FAIR + ale_min/ml/max/mean; nuova tabella kri

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 09:25:46 +02:00

72 lines
4.4 KiB
SQL

-- ============================================================================
-- Migration 026 - Risk quantitativo FAIR + KRI (P2)
-- ----------------------------------------------------------------------------
-- Affianca al risk register qualitativo (likelihood/impact 1-5) l'analisi
-- quantitativa FAIR (Factor Analysis of Information Risk) in valore economico:
-- - parametri FAIR su risks (TEF, vulnerability, loss magnitude PERT min/ml/max)
-- - ale_min / ale_ml / ale_max / ale_mean: Annualized Loss Expectancy (EUR)
-- calcolati via Monte Carlo e persistiti.
-- - tabella kri: Key Risk Indicators con soglie e valori correnti.
--
-- Idempotente. Rilanciabile.
-- mysql -h localhost nis2_agile_db -e "source docs/sql/026_risk_quantitative.sql"
-- ============================================================================
DELIMITER //
DROP PROCEDURE IF EXISTS _mig026_col //
CREATE PROCEDURE _mig026_col(IN col VARCHAR(64), IN ddl TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='risks' AND COLUMN_NAME=col) THEN
SET @sql = CONCAT('ALTER TABLE risks ADD COLUMN ', ddl);
PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st;
END IF;
END //
DELIMITER ;
-- Parametri FAIR (input)
CALL _mig026_col('fair_tef_min', "fair_tef_min DECIMAL(10,2) NULL COMMENT 'Threat Event Frequency min (eventi/anno)'");
CALL _mig026_col('fair_tef_ml', "fair_tef_ml DECIMAL(10,2) NULL COMMENT 'Threat Event Frequency most likely (eventi/anno)'");
CALL _mig026_col('fair_tef_max', "fair_tef_max DECIMAL(10,2) NULL COMMENT 'Threat Event Frequency max (eventi/anno)'");
CALL _mig026_col('fair_vuln', "fair_vuln DECIMAL(5,4) NULL COMMENT 'Vulnerability: prob. che una minaccia diventi perdita (0-1)'");
CALL _mig026_col('fair_lm_min', "fair_lm_min DECIMAL(14,2) NULL COMMENT 'Loss Magnitude min (EUR per evento)'");
CALL _mig026_col('fair_lm_ml', "fair_lm_ml DECIMAL(14,2) NULL COMMENT 'Loss Magnitude most likely (EUR per evento)'");
CALL _mig026_col('fair_lm_max', "fair_lm_max DECIMAL(14,2) NULL COMMENT 'Loss Magnitude max (EUR per evento)'");
-- Risultati FAIR (output ALE in EUR)
CALL _mig026_col('ale_min', "ale_min DECIMAL(16,2) NULL COMMENT 'Annualized Loss Expectancy - percentile basso (P10)'");
CALL _mig026_col('ale_ml', "ale_ml DECIMAL(16,2) NULL COMMENT 'Annualized Loss Expectancy - mediana (P50)'");
CALL _mig026_col('ale_max', "ale_max DECIMAL(16,2) NULL COMMENT 'Annualized Loss Expectancy - percentile alto (P90)'");
CALL _mig026_col('ale_mean', "ale_mean DECIMAL(16,2) NULL COMMENT 'Annualized Loss Expectancy - media simulazione'");
CALL _mig026_col('fair_computed_at',"fair_computed_at DATETIME NULL COMMENT 'Ultimo calcolo FAIR'");
DROP PROCEDURE IF EXISTS _mig026_col;
-- Key Risk Indicators
CREATE TABLE IF NOT EXISTS kri (
id INT NOT NULL AUTO_INCREMENT,
organization_id INT NOT NULL,
name VARCHAR(160) NOT NULL,
description VARCHAR(255) NULL,
category ENUM('cyber','operational','compliance','supply_chain','physical','human') NOT NULL DEFAULT 'cyber',
unit VARCHAR(40) NULL COMMENT 'Unita di misura (%, count, EUR, giorni)',
current_value DECIMAL(16,4) NULL,
target_value DECIMAL(16,4) NULL COMMENT 'Valore obiettivo/atteso',
threshold_warning DECIMAL(16,4) NULL COMMENT 'Soglia ambra',
threshold_critical DECIMAL(16,4) NULL COMMENT 'Soglia rossa',
direction ENUM('higher_worse','lower_worse') NOT NULL DEFAULT 'higher_worse' COMMENT 'Se valori alti=peggio o bassi=peggio',
status ENUM('green','amber','red','unknown') NOT NULL DEFAULT 'unknown',
linked_risk_id INT NULL,
measured_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_kri_org (organization_id),
KEY idx_kri_risk (linked_risk_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Key Risk Indicators con soglie e stato semaforo';
-- ROLLBACK:
-- DROP TABLE IF EXISTS kri;
-- ALTER TABLE risks DROP COLUMN fair_tef_min, DROP COLUMN fair_tef_ml, DROP COLUMN fair_tef_max,
-- DROP COLUMN fair_vuln, DROP COLUMN fair_lm_min, DROP COLUMN fair_lm_ml, DROP COLUMN fair_lm_max,
-- DROP COLUMN ale_min, DROP COLUMN ale_ml, DROP COLUMN ale_max, DROP COLUMN ale_mean, DROP COLUMN fair_computed_at;