domdp commited on
Commit
8d470cb
·
verified ·
1 Parent(s): 0a7ed84

Upload 3 files

Browse files

Initial deployment from Colab

Files changed (3) hide show
  1. readme.md +55 -0
  2. requirements.txt +15 -0
  3. stan+regex.py +1584 -0
readme.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Sistema Ibrido di Anonimizzazione Dati
3
+ emoji: 🔒
4
+ colorFrom: blue
5
+ colorTo: red
6
+ sdk: gradio
7
+ sdk_version: 4.44.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # 🔒 Sistema Ibrido di Anonimizzazione Dati
14
+
15
+ Un sistema avanzato per l'identificazione e anonimizzazione di dati sensibili in testi italiani, che combina:
16
+
17
+ - **Stanford Deidentifier**: Modello transformer specializzato
18
+ - **Microsoft Presidio**: Framework enterprise per data privacy
19
+ - **Regex personalizzate**: Pattern specifici per formati italiani
20
+
21
+ ## 🎯 Caratteristiche
22
+
23
+ - ✅ Riconoscimento di **10+ tipi di entità** (persone, CF, P.IVA, IBAN, telefoni, email, ecc.)
24
+ - 🎛️ **Controllo granulare** per ogni tipo di entità
25
+ - 🔄 **3 modalità di anonimizzazione**: sostituzione, oscuramento, pseudonimizzazione
26
+ - ⚙️ **Parametri avanzati** personalizzabili
27
+ - 📊 **Statistiche dettagliate** di rilevamento
28
+
29
+ ## 🚀 Utilizzo
30
+
31
+ 1. Inserisci il testo contenente dati sensibili
32
+ 2. Seleziona i tipi di entità da anonimizzare
33
+ 3. Configura il metodo di anonimizzazione
34
+ 4. Visualizza i risultati con entità evidenziate
35
+
36
+ ## 🔬 Tecnologie
37
+
38
+ - **Gradio** per l'interfaccia web
39
+ - **spaCy** per il processamento NLP
40
+ - **Transformers** per i modelli deep learning
41
+ - **Presidio** per la privacy dei dati
42
+
43
+ ## 📋 Esempi supportati
44
+
45
+ - **Persone**: Mario Rossi, Dott. Giovanni Bianchi
46
+ - **Codici Fiscali**: RSSMRC80D15H501V
47
+ - **Partite IVA**: IT12345678901
48
+ - **IBAN**: IT60X0542811101000000123456
49
+ - **Telefoni**: +39 333-123-4567
50
+ - **Email**: [email protected]
51
+ - **Targhe**: AB123CD
52
+
53
+ ---
54
+
55
+ Basato sulla ricerca comparativa di Small Language Models per l'anonimizzazione di dati in lingua italiana.
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ presidio-analyzer[transformers]
3
+ presidio-anonymizer
4
+ spacy>=3.8.0
5
+ spacy-transformers
6
+ spacy-alignments
7
+ transformers>=4.21.0
8
+ torch>=1.12.0
9
+ accelerate>=0.20.0
10
+ pandas>=1.3.0
11
+ numpy>=1.21.0
12
+
13
+ # Modelli spaCy (installati automaticamente)
14
+ https://github.com/explosion/spacy-models/releases/download/it_core_news_sm-3.8.0/it_core_news_sm-3.8.0-py3-none-any.whl
15
+ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
stan+regex.py ADDED
@@ -0,0 +1,1584 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # =========================================================
3
+ # CELLA 1: IMPORT E SETUP INIZIALE
4
+ # =========================================================
5
+
6
+ import os
7
+ import re
8
+ import gradio as gr
9
+ import pandas as pd
10
+ import json
11
+ from typing import List, Dict, Tuple, Any
12
+ import spacy
13
+ import torch
14
+ from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
15
+
16
+ # Import Presidio
17
+ from presidio_analyzer import AnalyzerEngine, RecognizerRegistry, PatternRecognizer
18
+ from presidio_analyzer.pattern_recognizer import Pattern
19
+ from presidio_analyzer.nlp_engine import NlpEngine, NlpEngineProvider
20
+ from presidio_analyzer.context_aware_enhancers import LemmaContextAwareEnhancer
21
+ from presidio_anonymizer import AnonymizerEngine
22
+ from presidio_anonymizer.entities import OperatorConfig
23
+
24
+ # Configurazione base
25
+ print("✅ Import completati!")
26
+
27
+ # =========================================================
28
+ # CELLA 2: CONFIGURAZIONE RICONOSCITORI PERSONALIZZATI (CORRETTA)
29
+ # =========================================================
30
+
31
+ def create_italian_recognizers():
32
+ """
33
+ Crea riconoscitori personalizzati per il contesto italiano
34
+ """
35
+ recognizers = []
36
+
37
+ # CODICE FISCALE
38
+ cf_patterns = [Pattern(name="codice fiscale",
39
+ regex=r"\b[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]\b",
40
+ score=0.9)]
41
+ cf_recognizer = PatternRecognizer(
42
+ supported_entity="CODICE_FISCALE",
43
+ patterns=cf_patterns,
44
+ context=["codice", "fiscale", "cf", "c.f.", "cod.fisc.", "codice fiscale"],
45
+ supported_language="en" # Aggiungiamo il supporto per l'inglese
46
+ )
47
+ recognizers.append(cf_recognizer)
48
+
49
+ # PARTITA IVA
50
+ piva_patterns = [Pattern(name="partita iva",
51
+ regex=r"\b(IT)?[0-9]{11}\b",
52
+ score=0.85)]
53
+ piva_recognizer = PatternRecognizer(
54
+ supported_entity="PARTITA_IVA",
55
+ patterns=piva_patterns,
56
+ context=["partita", "iva", "p.iva", "p. iva", "piva", "partita iva"],
57
+ supported_language="en"
58
+ )
59
+ recognizers.append(piva_recognizer)
60
+
61
+ # IBAN ITALIANO
62
+ iban_patterns = [Pattern(name="iban",
63
+ regex=r"\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}\b",
64
+ score=0.9)]
65
+ iban_recognizer = PatternRecognizer(
66
+ supported_entity="IBAN_CODE",
67
+ patterns=iban_patterns,
68
+ context=["iban", "bonifico", "bancario", "conto", "pagamento", "IBAN"],
69
+ supported_language="en"
70
+ )
71
+ recognizers.append(iban_recognizer)
72
+
73
+ # TARGA ITALIANA
74
+ targa_patterns = [Pattern(name="targa",
75
+ regex=r"\b[A-Z]{2}[0-9]{3}[A-Z]{2}\b",
76
+ score=0.85)]
77
+ targa_recognizer = PatternRecognizer(
78
+ supported_entity="TARGA",
79
+ patterns=targa_patterns,
80
+ context=["targa", "auto", "veicolo", "automobile", "macchina"],
81
+ supported_language="en"
82
+ )
83
+ recognizers.append(targa_recognizer)
84
+
85
+ # TELEFONO ITALIANO
86
+ telefono_patterns = [
87
+ Pattern(name="telefono (con prefisso)", regex=r"\b\+39\s?[0-9]{10}\b", score=0.9),
88
+ Pattern(name="telefono (cellulare)", regex=r"\b[3][0-9]{9}\b", score=0.8),
89
+ Pattern(name="telefono (fisso)", regex=r"\b0[0-9]{1,3}[-\s]?[0-9]{7}\b", score=0.7),
90
+ Pattern(name="telefono (generico)", regex=r"\b[0-9]{10}\b", score=0.6)
91
+ ]
92
+ telefono_recognizer = PatternRecognizer(
93
+ supported_entity="PHONE_NUMBER",
94
+ patterns=telefono_patterns,
95
+ context=["telefono", "cellulare", "tel", "chiamare", "contattare", "mobile"],
96
+ supported_language="en"
97
+ )
98
+ recognizers.append(telefono_recognizer)
99
+
100
+ # DATA ITALIANA
101
+ data_patterns = [
102
+ Pattern(name="data (dd/mm/yyyy)", regex=r"\b[0-3][0-9]/[0-1][0-9]/[1-2][0-9]{3}\b", score=0.9),
103
+ Pattern(name="data (dd-mm-yyyy)", regex=r"\b[0-3][0-9]-[0-1][0-9]-[1-2][0-9]{3}\b", score=0.9),
104
+ Pattern(name="data (d/m/yyyy)", regex=r"\b[1-9]/[1-9]/[1-2][0-9]{3}\b", score=0.8),
105
+ Pattern(name="data (dd/mm/yy)", regex=r"\b[0-3][0-9]/[0-1][0-9]/[0-9]{2}\b", score=0.8)
106
+ ]
107
+ data_recognizer = PatternRecognizer(
108
+ supported_entity="DATE_TIME",
109
+ patterns=data_patterns,
110
+ context=["nato", "nata", "data di nascita", "nasce", "data", "nascita"],
111
+ supported_language="en"
112
+ )
113
+ recognizers.append(data_recognizer)
114
+
115
+ print(f"✅ Creati {len(recognizers)} riconoscitori personalizzati")
116
+ return recognizers
117
+
118
+ # Crea i riconoscitori
119
+ italian_recognizers = create_italian_recognizers()
120
+
121
+ # =========================================================
122
+ # CELLA 3: STANFORD COME RECOGNIZER SEPARATO
123
+ # =========================================================
124
+
125
+ from presidio_analyzer import EntityRecognizer, RecognizerResult
126
+ from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
127
+ import torch
128
+
129
+ class StanfordRecognizer(EntityRecognizer):
130
+ def __init__(self):
131
+ self.supported_entities = ["PERSON", "ORGANIZATION", "LOCATION", "DATE_TIME", "AGE", "PHONE_NUMBER", "EMAIL"]
132
+ self.supported_language = "en"
133
+
134
+ # Carica il modello Stanford
135
+ try:
136
+ self.tokenizer = AutoTokenizer.from_pretrained("StanfordAIMI/stanford-deidentifier-base")
137
+ self.model = AutoModelForTokenClassification.from_pretrained("StanfordAIMI/stanford-deidentifier-base")
138
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
139
+ self.model.to(self.device)
140
+ self.model.eval()
141
+
142
+ # Crea una pipeline per gestire più facilmente il modello
143
+ self.pipeline = pipeline(
144
+ "token-classification",
145
+ model=self.model,
146
+ tokenizer=self.tokenizer,
147
+ device=0 if torch.cuda.is_available() else -1,
148
+ aggregation_strategy="max"
149
+ )
150
+ print("✅ Modello Stanford caricato con successo!")
151
+ except Exception as e:
152
+ print(f"⚠️ Errore nel caricamento del modello Stanford: {e}")
153
+ self.pipeline = None
154
+
155
+ super().__init__(supported_entities=self.supported_entities, supported_language=self.supported_language)
156
+
157
+ def analyze(self, text, entities, nlp_artifacts):
158
+ """
159
+ Analizza il testo e restituisce RecognizerResult
160
+ """
161
+ results = []
162
+
163
+ if self.pipeline is None:
164
+ return results
165
+
166
+ try:
167
+ # Usa la pipeline per processare il testo
168
+ outputs = self.pipeline(text)
169
+
170
+ for output in outputs:
171
+ # Mappa le etichette del modello Stanford a quelle di Presidio
172
+ stanford_to_presidio = {
173
+ "PATIENT": "PERSON",
174
+ "STAFF": "PERSON",
175
+ "HOSP": "ORGANIZATION",
176
+ "HOSPITAL": "ORGANIZATION",
177
+ "AGE": "AGE",
178
+ "DATE": "DATE_TIME",
179
+ "PHONE": "PHONE_NUMBER",
180
+ "PER": "PERSON",
181
+ "LOC": "LOCATION",
182
+ "ORG": "ORGANIZATION",
183
+ "PERSON": "PERSON",
184
+ "LOCATION": "LOCATION",
185
+ "ORGANIZATION": "ORGANIZATION"
186
+ }
187
+
188
+ entity_type = output.get("entity_group", "")
189
+ # Rimuovi prefissi B-, I- se presenti
190
+ if entity_type.startswith(("B-", "I-")):
191
+ entity_type = entity_type[2:]
192
+
193
+ # Mappa all'entità Presidio
194
+ presidio_entity = stanford_to_presidio.get(entity_type, entity_type)
195
+
196
+ # Crea RecognizerResult se l'entità è supportata
197
+ if presidio_entity in self.supported_entities:
198
+ result = RecognizerResult(
199
+ entity_type=presidio_entity,
200
+ start=output["start"],
201
+ end=output["end"],
202
+ score=output["score"]
203
+ )
204
+ results.append(result)
205
+
206
+ except Exception as e:
207
+ print(f"Errore nell'analisi Stanford: {e}")
208
+
209
+ return results
210
+
211
+ def load(self):
212
+ pass # Il caricamento è fatto nel costruttore
213
+
214
+ # Crea un'istanza del recognizer Stanford
215
+ stanford_recognizer = StanfordRecognizer()
216
+
217
+ # Se l'analyzer è già stato creato, aggiungi il recognizer Stanford
218
+ if 'analyzer' in globals():
219
+ try:
220
+ analyzer.registry.add_recognizer(stanford_recognizer)
221
+ print("✅ Stanford recognizer aggiunto a Presidio")
222
+ except Exception as e:
223
+ print(f"⚠️ Errore nell'aggiunta di Stanford recognizer: {e}")
224
+
225
+ # =========================================================
226
+ # CELLA 4: SISTEMA DI REGEX FALLBACK
227
+ # =========================================================
228
+
229
+ class RegexFallbackEngine:
230
+ def __init__(self):
231
+ self.patterns = {
232
+ "PERSON": [
233
+ r"\b[A-Z][a-z]+\s+[A-Z][a-z]+\b", # Nome Cognome
234
+ r"\b(?:Sig\.|Dott\.|Dr\.|Ing\.)\s+[A-Z][a-z]+\s+[A-Z][a-z]+\b", # Titolo + Nome Cognome
235
+ ],
236
+ "CODICE_FISCALE": [
237
+ r"\b[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]\b",
238
+ ],
239
+ "PARTITA_IVA": [
240
+ r"\b(?:IT)?\d{11}\b",
241
+ ],
242
+ "DATE_TIME": [
243
+ r"\b\d{1,2}[/\-\.]\d{1,2}[/\-\.]\d{2,4}\b",
244
+ r"\b\d{1,2}\s+(?:gennaio|febbraio|marzo|aprile|maggio|giugno|luglio|agosto|settembre|ottobre|novembre|dicembre)\s+\d{4}\b",
245
+ ],
246
+ "PHONE_NUMBER": [
247
+ r"\b\+39\s?\d{10}\b",
248
+ r"\b\d{10}\b",
249
+ r"\b0\d{1,3}[-\.\s]?\d{7}\b",
250
+ ],
251
+ "EMAIL": [
252
+ r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
253
+ ],
254
+ "IBAN_CODE": [
255
+ r"\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}[A-Z0-9]{12}\b",
256
+ ],
257
+ "TARGA": [
258
+ r"\b[A-Z]{2}\d{3}[A-Z]{2}\b",
259
+ ]
260
+ }
261
+
262
+ def analyze(self, text):
263
+ """
264
+ Analizza il testo utilizzando regex
265
+ """
266
+ results = []
267
+
268
+ for entity_type, patterns in self.patterns.items():
269
+ for pattern in patterns:
270
+ for match in re.finditer(pattern, text):
271
+ results.append({
272
+ "entity_type": entity_type,
273
+ "start": match.start(),
274
+ "end": match.end(),
275
+ "text": match.group(),
276
+ "score": 0.9 # Assegna un punteggio fisso per regex
277
+ })
278
+
279
+ return results
280
+
281
+ # Inizializza il sistema di regex fallback
282
+ regex_engine = RegexFallbackEngine()
283
+
284
+ # =========================================================
285
+ # CELLA 5: CONFIGURAZIONE PRESIDIO SEMPLIFICATA
286
+ # =========================================================
287
+
288
+ # Per ora, creiamo una configurazione base senza Stanford, che possiamo aggiungere come recognizer separato
289
+ from presidio_analyzer import AnalyzerEngine
290
+ from presidio_analyzer.predefined_recognizers import (
291
+ PhoneRecognizer, EmailRecognizer, CreditCardRecognizer, IbanRecognizer
292
+ )
293
+
294
+ def setup_presidio_simple():
295
+ """
296
+ Configura Presidio con setup semplificato
297
+ """
298
+ try:
299
+ # Crea l'analyzer engine con configurazione di base
300
+ analyzer = AnalyzerEngine()
301
+
302
+ # Aggiungi riconoscitori predefiniti
303
+ try:
304
+ analyzer.registry.add_recognizer(PhoneRecognizer())
305
+ except:
306
+ pass
307
+
308
+ try:
309
+ analyzer.registry.add_recognizer(EmailRecognizer())
310
+ except:
311
+ pass
312
+
313
+ try:
314
+ analyzer.registry.add_recognizer(CreditCardRecognizer())
315
+ except:
316
+ pass
317
+
318
+ try:
319
+ analyzer.registry.add_recognizer(IbanRecognizer())
320
+ except:
321
+ pass
322
+
323
+ # Aggiungi riconoscitori personalizzati se definiti
324
+ if 'italian_recognizers' in globals():
325
+ for recognizer in italian_recognizers:
326
+ try:
327
+ analyzer.registry.add_recognizer(recognizer)
328
+ except Exception as e:
329
+ print(f"Errore aggiungendo recognizer: {e}")
330
+
331
+ # Crea l'anonymizer engine
332
+ anonymizer = AnonymizerEngine()
333
+
334
+ print("✅ Presidio configurato con successo!")
335
+ return analyzer, anonymizer
336
+
337
+ except Exception as e:
338
+ print(f"❌ Errore nella configurazione di Presidio: {e}")
339
+ # Fallback a configurazione minima
340
+ analyzer = AnalyzerEngine()
341
+ anonymizer = AnonymizerEngine()
342
+ print("⚠️ Usando configurazione di default")
343
+ return analyzer, anonymizer
344
+
345
+ # Inizializza Presidio
346
+ analyzer, anonymizer = setup_presidio_simple()
347
+
348
+ # =========================================================
349
+ # CELLA 6: SISTEMA DI ANONIMIZZAZIONE IBRIDO (CORRETTO)
350
+ # =========================================================
351
+
352
+ class HybridAnonymizer:
353
+ def __init__(self, presidio_analyzer, regex_engine, anonymizer):
354
+ self.presidio_analyzer = presidio_analyzer
355
+ self.regex_engine = regex_engine
356
+ self.anonymizer = anonymizer
357
+
358
+ def analyze_text(self, text, enable_stanford=True, enable_regex=True):
359
+ """
360
+ Analizza il testo usando tutti i metodi disponibili
361
+ """
362
+ all_entities = []
363
+
364
+ # Presidio ora include Stanford tramite il recognizer aggiunto
365
+ presidio_results = self.presidio_analyzer.analyze(
366
+ text=text,
367
+ language="en",
368
+ entities=None, # Usa tutti i recognizer disponibili
369
+ allow_list=None
370
+ )
371
+
372
+ # Converti risultati Presidio
373
+ for result in presidio_results:
374
+ all_entities.append({
375
+ "entity_type": result.entity_type,
376
+ "start": result.start,
377
+ "end": result.end,
378
+ "text": text[result.start:result.end],
379
+ "score": result.score,
380
+ "source": "presidio"
381
+ })
382
+
383
+ # Aggiungi regex se abilitato
384
+ if enable_regex:
385
+ try:
386
+ regex_entities = self.regex_engine.analyze(text)
387
+ for entity in regex_entities:
388
+ all_entities.append({
389
+ "entity_type": entity["entity_type"],
390
+ "start": entity["start"],
391
+ "end": entity["end"],
392
+ "text": entity["text"],
393
+ "score": entity["score"],
394
+ "source": "regex"
395
+ })
396
+ except Exception as e:
397
+ print(f"Errore in Regex: {e}")
398
+
399
+ return self._merge_overlapping_entities(all_entities)
400
+
401
+ def _merge_overlapping_entities(self, entities):
402
+ if not entities:
403
+ return []
404
+
405
+ entities.sort(key=lambda x: (x["start"], -x["score"]))
406
+ merged = []
407
+
408
+ for entity in entities:
409
+ if not merged or merged[-1]["end"] <= entity["start"]:
410
+ merged.append(entity)
411
+ elif entity["score"] > merged[-1]["score"]:
412
+ merged[-1] = entity
413
+
414
+ return merged
415
+
416
+ def anonymize_text(self, text, entities, anonymization_type="replace"):
417
+ """
418
+ Anonimizza il testo basandosi sulle entità trovate con diversi metodi
419
+
420
+ Tipi di anonimizzazione:
421
+ - replace: sostituisce con tag (es. <PERSON>)
422
+ - redact: oscura con asterischi (es. ******)
423
+ - pseudonymize: sostituisce con valori fittizi (es. Persona1)
424
+ """
425
+ if not entities:
426
+ return text
427
+
428
+ if anonymization_type == "replace":
429
+ # Usa Presidio per sostituire le entità con tag
430
+ presidio_results = []
431
+ for entity in entities:
432
+ from presidio_analyzer import RecognizerResult
433
+ presidio_results.append(
434
+ RecognizerResult(
435
+ entity_type=entity["entity_type"],
436
+ start=entity["start"],
437
+ end=entity["end"],
438
+ score=entity["score"]
439
+ )
440
+ )
441
+
442
+ # Configura l'anonimizzazione con tag
443
+ operators = {
444
+ "PERSON": OperatorConfig("replace", {"new_value": "<PERSON>"}),
445
+ "LOCATION": OperatorConfig("replace", {"new_value": "<LOCATION>"}),
446
+ "ORGANIZATION": OperatorConfig("replace", {"new_value": "<ORGANIZATION>"}),
447
+ "DATE_TIME": OperatorConfig("replace", {"new_value": "<DATE>"}),
448
+ "PHONE_NUMBER": OperatorConfig("replace", {"new_value": "<PHONE>"}),
449
+ "EMAIL_ADDRESS": OperatorConfig("replace", {"new_value": "<EMAIL>"}),
450
+ "IBAN_CODE": OperatorConfig("replace", {"new_value": "<IBAN>"}),
451
+ "CODICE_FISCALE": OperatorConfig("replace", {"new_value": "<CF>"}),
452
+ "PARTITA_IVA": OperatorConfig("replace", {"new_value": "<PIVA>"}),
453
+ "TARGA": OperatorConfig("replace", {"new_value": "<TARGA>"}),
454
+ "AGE": OperatorConfig("replace", {"new_value": "<AGE>"})
455
+ }
456
+
457
+ anonymized_result = self.anonymizer.anonymize(
458
+ text=text,
459
+ analyzer_results=presidio_results,
460
+ operators=operators
461
+ )
462
+
463
+ return anonymized_result.text
464
+
465
+ elif anonymization_type == "redact":
466
+ # Sostituisce ogni entità con asterischi
467
+ anonymized = text
468
+ # Ordina le entità per posizione (dall'ultima alla prima) per non alterare gli indici
469
+ sorted_entities = sorted(entities, key=lambda x: x["start"], reverse=True)
470
+
471
+ for entity in sorted_entities:
472
+ # Genera asterischi della stessa lunghezza dell'entità
473
+ asterisks = "*" * (entity["end"] - entity["start"])
474
+ # Sostituisci il testo
475
+ anonymized = anonymized[:entity["start"]] + asterisks + anonymized[entity["end"]:]
476
+
477
+ return anonymized
478
+
479
+ elif anonymization_type == "pseudonymize":
480
+ # Sostituisce ogni entità con un valore fittizio
481
+ anonymized = text
482
+
483
+ # Dizionario per tenere traccia dei valori fittizi generati
484
+ pseudonyms = {}
485
+ type_counts = {}
486
+
487
+ # Ordina le entità per posizione (dall'ultima alla prima) per non alterare gli indici
488
+ sorted_entities = sorted(entities, key=lambda x: x["start"], reverse=True)
489
+
490
+ for entity in sorted_entities:
491
+ entity_type = entity["entity_type"]
492
+ entity_text = entity["text"]
493
+
494
+ # Se questa entità è già stata sostituita in precedenza, usa lo stesso valore
495
+ if entity_text in pseudonyms:
496
+ new_value = pseudonyms[entity_text]
497
+ else:
498
+ # Inizializza il contatore se non esiste
499
+ if entity_type not in type_counts:
500
+ type_counts[entity_type] = 0
501
+
502
+ # Incrementa il contatore
503
+ type_counts[entity_type] += 1
504
+
505
+ # Genera un valore fittizio basato sul tipo di entità
506
+ if entity_type == "PERSON":
507
+ new_value = f"Persona{type_counts[entity_type]}"
508
+ elif entity_type == "LOCATION":
509
+ new_value = f"Luogo{type_counts[entity_type]}"
510
+ elif entity_type == "ORGANIZATION":
511
+ new_value = f"Organizzazione{type_counts[entity_type]}"
512
+ elif entity_type == "DATE_TIME":
513
+ new_value = f"Data{type_counts[entity_type]}"
514
+ elif entity_type == "PHONE_NUMBER":
515
+ new_value = f"+39-XXX-XXX-{1000+type_counts[entity_type]}"
516
+ elif entity_type == "EMAIL_ADDRESS" or entity_type == "EMAIL":
517
+ new_value = f"email{type_counts[entity_type]}@esempio.com"
518
+ elif entity_type == "IBAN_CODE":
519
+ new_value = f"IT00X0000000000000{type_counts[entity_type]}"
520
+ elif entity_type == "CODICE_FISCALE" or entity_type == "CF":
521
+ new_value = f"ABCDEF00G00H000{type_counts[entity_type]}"
522
+ elif entity_type == "PARTITA_IVA" or entity_type == "PIVA":
523
+ new_value = f"IT0000000000{type_counts[entity_type]}"
524
+ elif entity_type == "TARGA":
525
+ new_value = f"XX000{type_counts[entity_type]}"
526
+ elif entity_type == "AGE":
527
+ new_value = f"XX"
528
+ else:
529
+ new_value = f"{entity_type}{type_counts[entity_type]}"
530
+
531
+ # Memorizza il valore generato per riusi futuri
532
+ pseudonyms[entity_text] = new_value
533
+
534
+ # Sostituisci il testo
535
+ anonymized = anonymized[:entity["start"]] + new_value + anonymized[entity["end"]:]
536
+
537
+ return anonymized
538
+
539
+ else:
540
+ # Tipo di anonimizzazione non supportato, usa replace come fallback
541
+ print(f"Tipo di anonimizzazione non supportato: {anonymization_type}, usando 'replace'")
542
+ return self.anonymize_text(text, entities, "replace")
543
+
544
+ # Inizializza il sistema ibrido
545
+ hybrid_anonymizer = HybridAnonymizer(analyzer, regex_engine, anonymizer)
546
+
547
+ # =========================================================
548
+ # CELLA 7: UTILITÀ DI VISUALIZZAZIONE
549
+ # =========================================================
550
+
551
+ # Colori per i diversi tipi di entità
552
+ ENTITY_COLORS = {
553
+ "PERSON": "#ff7f50", # Corallo
554
+ "LOCATION": "#6495ed", # Azzurro
555
+ "ORGANIZATION": "#9acd32", # Verde
556
+ "DATE_TIME": "#ffa500", # Arancione
557
+ "PHONE_NUMBER": "#da70d6", # Orchidea
558
+ "EMAIL_ADDRESS": "#dda0dd", # Plum
559
+ "IBAN_CODE": "#1e90ff", # Blu
560
+ "CODICE_FISCALE": "#ff69b4", # Rosa
561
+ "PARTITA_IVA": "#ff69b4", # Rosa
562
+ "TARGA": "#bdb76b" # Kaki
563
+ }
564
+
565
+ def highlight_entities_html(text, entities):
566
+ """
567
+ Evidenzia le entità trovate nel testo con colori
568
+ """
569
+ if not entities:
570
+ return text
571
+
572
+ # Prepara HTML con span colorati
573
+ chars = list(text)
574
+ spans = []
575
+
576
+ for entity in entities:
577
+ entity_type = entity["entity_type"]
578
+ source = entity.get("source", "unknown")
579
+ color = ENTITY_COLORS.get(entity_type, "#cccccc")
580
+ score = int(entity["score"] * 100)
581
+
582
+ # Tooltip con informazioni dettagliate
583
+ tooltip = f"{entity_type} ({score}%) - detected by {source}"
584
+
585
+ spans.append({
586
+ "index": entity["start"],
587
+ "content": f'<span style="background-color: {color}; padding: 2px; border-radius: 3px;" title="{tooltip}">',
588
+ "is_opening": True
589
+ })
590
+
591
+ spans.append({
592
+ "index": entity["end"],
593
+ "content": '</span>',
594
+ "is_opening": False
595
+ })
596
+
597
+ # Ordina i span (chiusura prima dell'apertura se stesso indice)
598
+ spans.sort(key=lambda x: (x["index"], not x["is_opening"]))
599
+
600
+ # Inserisce i tag span nel testo
601
+ offset = 0
602
+ for span in spans:
603
+ adjusted_index = span["index"] + offset
604
+ chars.insert(adjusted_index, span["content"])
605
+ offset += 1
606
+
607
+ return "".join(chars)
608
+
609
+ def generate_statistics(entities):
610
+ """
611
+ Genera statistiche sulle entità rilevate
612
+ """
613
+ stats = {
614
+ "total_entities": len(entities),
615
+ "by_type": {},
616
+ "by_source": {},
617
+ "avg_confidence": 0,
618
+ "all_detected_types": set()
619
+ }
620
+
621
+ for entity in entities:
622
+ entity_type = entity["entity_type"]
623
+ source = entity.get("source", "unknown")
624
+ score = entity["score"]
625
+
626
+ # Count by type
627
+ stats["by_type"][entity_type] = stats["by_type"].get(entity_type, 0) + 1
628
+
629
+ # Count by source
630
+ stats["by_source"][source] = stats["by_source"].get(source, 0) + 1
631
+
632
+ # Track all detected types
633
+ stats["all_detected_types"].add(entity_type)
634
+
635
+ # Update average confidence
636
+ stats["avg_confidence"] += score
637
+
638
+ if entities:
639
+ stats["avg_confidence"] /= len(entities)
640
+
641
+ stats["all_detected_types"] = list(stats["all_detected_types"])
642
+
643
+ return stats
644
+
645
+ # =========================================================
646
+ # CELLA 8: INTERFACCIA GRADIO (MODIFICHE)
647
+ # =========================================================
648
+
649
+ def process_text_gradio(text, anonymization_type, use_stanford, use_regex, confidence_threshold):
650
+ """
651
+ Processa il testo con l'interfaccia Gradio
652
+ """
653
+ # Verifica che il testo sia una stringa
654
+ if not isinstance(text, str):
655
+ return "Errore: input deve essere una stringa", "", "Errore: tipo di input non valido"
656
+
657
+ if not text.strip():
658
+ return "", "", "Nessun testo fornito"
659
+
660
+ try:
661
+ # Analizza il testo
662
+ entities = hybrid_anonymizer.analyze_text(
663
+ text,
664
+ enable_stanford=use_stanford,
665
+ enable_regex=use_regex
666
+ )
667
+
668
+ # Filtra per confidenza
669
+ filtered_entities = [e for e in entities if e["score"] >= confidence_threshold]
670
+
671
+ # Genera HTML evidenziato
672
+ highlighted_html = highlight_entities_html(text, filtered_entities)
673
+
674
+ # Anonimizza il testo
675
+ anonymized_text = hybrid_anonymizer.anonymize_text(text, filtered_entities, anonymization_type)
676
+
677
+ # Genera statistiche
678
+ stats = generate_statistics(filtered_entities)
679
+
680
+ # Formatta le statistiche per Gradio
681
+ stats_str = f"""
682
+ **Statistiche rilevamento:**
683
+ - Entità totali trovate: {stats['total_entities']}
684
+ - Confidenza media: {stats['avg_confidence']:.2%}
685
+ - Tipi di entità rilevati: {', '.join(sorted(stats['all_detected_types']))}
686
+
687
+ **Per tipo:**
688
+ {chr(10).join([f"- {k}: {v}" for k, v in stats['by_type'].items()])}
689
+
690
+ **Per sorgente:**
691
+ {chr(10).join([f"- {k}: {v}" for k, v in stats['by_source'].items()])}
692
+ """
693
+
694
+ return highlighted_html, anonymized_text, stats_str
695
+
696
+ except Exception as e:
697
+ import traceback
698
+ error_msg = f"Errore: {str(e)}\n{traceback.format_exc()}"
699
+ return error_msg, "", error_msg
700
+
701
+ # =========================================================
702
+ # CELLA 9: INTERFACCIA DI CONTROLLO ENTITÀ (VERSIONE COMPLETA)
703
+ # =========================================================
704
+
705
+ def process_text_with_entity_control(
706
+ text,
707
+ anonymization_type,
708
+ use_stanford,
709
+ use_regex,
710
+ confidence_threshold,
711
+ person_enabled,
712
+ location_enabled,
713
+ organization_enabled,
714
+ date_time_enabled,
715
+ phone_number_enabled,
716
+ email_enabled,
717
+ iban_enabled,
718
+ codice_fiscale_enabled,
719
+ partita_iva_enabled,
720
+ targa_enabled,
721
+ # Nuovi parametri di anonimizzazione
722
+ tag_format="<TAG>",
723
+ redact_char="*",
724
+ preserve_length=False,
725
+ # Anonimizzazione per tipo specifico
726
+ person_anon_method=None,
727
+ location_anon_method=None,
728
+ organization_anon_method=None,
729
+ date_time_anon_method=None,
730
+ phone_anon_method=None,
731
+ email_anon_method=None,
732
+ iban_anon_method=None,
733
+ cf_anon_method=None,
734
+ piva_anon_method=None,
735
+ targa_anon_method=None,
736
+ # Soglie di confidenza specifiche per tipo
737
+ person_threshold=None,
738
+ location_threshold=None,
739
+ organization_threshold=None,
740
+ date_time_threshold=None,
741
+ phone_threshold=None,
742
+ email_threshold=None,
743
+ iban_threshold=None,
744
+ cf_threshold=None,
745
+ piva_threshold=None,
746
+ targa_threshold=None,
747
+ # Formati dei pseudonimi
748
+ person_pseudo_format="Persona{num}",
749
+ location_pseudo_format="Luogo{num}",
750
+ organization_pseudo_format="Organizzazione{num}",
751
+ date_pseudo_format="Data{num}",
752
+ phone_pseudo_format="+39-XXX-XXX-{num}",
753
+ email_pseudo_format="email{num}@esempio.com",
754
+ iban_pseudo_format="IT00X0000000000000{num}",
755
+ cf_pseudo_format="ABCDEF00G00H000{num}",
756
+ piva_pseudo_format="IT0000000000{num}",
757
+ targa_pseudo_format="XX000{num}"
758
+ ):
759
+ """
760
+ Processa il testo con controllo sulle entità da estrarre/anonimizzare
761
+ e con parametri avanzati di anonimizzazione
762
+ """
763
+ # Verifica che il testo sia una stringa
764
+ if not isinstance(text, str):
765
+ return "Errore: input deve essere una stringa", "", "Errore: tipo di input non valido"
766
+
767
+ if not text.strip():
768
+ return "", "", "Nessun testo fornito"
769
+
770
+ try:
771
+ # Crea una lista di entità abilitate e mappa dei metodi per tipo
772
+ enabled_entities = []
773
+ entity_anon_methods = {}
774
+ entity_thresholds = {}
775
+ entity_pseudo_formats = {}
776
+
777
+ # Mappa degli entity types, abilitazione, metodi, soglie e formati
778
+ entity_config = [
779
+ ("PERSON", person_enabled, person_anon_method, person_threshold, person_pseudo_format),
780
+ ("LOCATION", location_enabled, location_anon_method, location_threshold, location_pseudo_format),
781
+ ("ORGANIZATION", organization_enabled, organization_anon_method, organization_threshold, organization_pseudo_format),
782
+ ("DATE_TIME", date_time_enabled, date_time_anon_method, date_time_threshold, date_pseudo_format),
783
+ ("PHONE_NUMBER", phone_number_enabled, phone_anon_method, phone_threshold, phone_pseudo_format),
784
+ ("EMAIL", email_enabled, email_anon_method, email_threshold, email_pseudo_format),
785
+ ("EMAIL_ADDRESS", email_enabled, email_anon_method, email_threshold, email_pseudo_format),
786
+ ("IBAN_CODE", iban_enabled, iban_anon_method, iban_threshold, iban_pseudo_format),
787
+ ("CODICE_FISCALE", codice_fiscale_enabled, cf_anon_method, cf_threshold, cf_pseudo_format),
788
+ ("PARTITA_IVA", partita_iva_enabled, piva_anon_method, piva_threshold, piva_pseudo_format),
789
+ ("TARGA", targa_enabled, targa_anon_method, targa_threshold, targa_pseudo_format)
790
+ ]
791
+
792
+ # Popola gli array basandosi sulla configurazione
793
+ for entity_type, is_enabled, anon_method, threshold, pseudo_format in entity_config:
794
+ if is_enabled:
795
+ enabled_entities.append(entity_type)
796
+ # Se è specificato un metodo specifico per questo tipo, usalo
797
+ if anon_method:
798
+ entity_anon_methods[entity_type] = anon_method
799
+ # Se è specificata una soglia specifica per questo tipo, usala
800
+ if threshold is not None:
801
+ entity_thresholds[entity_type] = threshold
802
+ # Salva il formato del pseudonimo per questo tipo
803
+ entity_pseudo_formats[entity_type] = pseudo_format
804
+
805
+ # Se nessuna entità è abilitata, mostra il testo originale
806
+ if not enabled_entities:
807
+ return text, text, "Nessuna entità selezionata per l'anonimizzazione"
808
+
809
+ # Analizza il testo
810
+ entities = hybrid_anonymizer.analyze_text(
811
+ text,
812
+ enable_stanford=use_stanford,
813
+ enable_regex=use_regex
814
+ )
815
+
816
+ # Filtra per confidenza e per tipo di entità abilitato, usando soglie specifiche per tipo se disponibili
817
+ filtered_entities = []
818
+ for e in entities:
819
+ if e["entity_type"] in enabled_entities:
820
+ # Determina la soglia da usare
821
+ entity_threshold = entity_thresholds.get(e["entity_type"], confidence_threshold)
822
+ if e["score"] >= entity_threshold:
823
+ filtered_entities.append(e)
824
+
825
+ # Genera HTML evidenziato
826
+ highlighted_html = highlight_entities_html(text, filtered_entities)
827
+
828
+ # Anonimizza il testo con i parametri avanzati
829
+ anonymized_text = advanced_anonymize_text(
830
+ text,
831
+ filtered_entities,
832
+ anonymization_type,
833
+ tag_format=tag_format,
834
+ redact_char=redact_char,
835
+ preserve_length=preserve_length,
836
+ entity_anon_methods=entity_anon_methods,
837
+ entity_pseudo_formats=entity_pseudo_formats
838
+ )
839
+
840
+ # Genera statistiche
841
+ stats = generate_statistics(filtered_entities)
842
+
843
+ # Formatta le statistiche per Gradio
844
+ stats_str = f"""
845
+ **Statistiche rilevamento:**
846
+ - Entità totali trovate: {stats['total_entities']}
847
+ - Confidenza media: {stats['avg_confidence']:.2%}
848
+ - Tipi di entità rilevati: {', '.join(sorted(stats['all_detected_types']))}
849
+
850
+ **Per tipo:**
851
+ {chr(10).join([f"- {k}: {v}" for k, v in stats['by_type'].items()])}
852
+
853
+ **Per sorgente:**
854
+ {chr(10).join([f"- {k}: {v}" for k, v in stats['by_source'].items()])}
855
+
856
+ **Parametri di anonimizzazione:**
857
+ - Metodo globale: {anonymization_type}
858
+ - Formato tag: {tag_format}
859
+ - Preserva lunghezza: {"Sì" if preserve_length else "No"}
860
+ """
861
+ # Aggiungi informazioni sui metodi specifici
862
+ if entity_anon_methods:
863
+ stats_str += "\n**Metodi specifici per tipo:**\n"
864
+ stats_str += chr(10).join([f"- {k}: {v}" for k, v in entity_anon_methods.items()])
865
+
866
+ # Aggiungi informazioni sulle soglie specifiche
867
+ if entity_thresholds:
868
+ stats_str += "\n\n**Soglie di confidenza specifiche:**\n"
869
+ stats_str += chr(10).join([f"- {k}: {v}" for k, v in entity_thresholds.items()])
870
+
871
+ return highlighted_html, anonymized_text, stats_str
872
+
873
+ except Exception as e:
874
+ import traceback
875
+ error_msg = f"Errore: {str(e)}\n{traceback.format_exc()}"
876
+ return error_msg, "", error_msg
877
+
878
+ def advanced_anonymize_text(text, entities, global_anon_type, tag_format="<TAG>", redact_char="*",
879
+ preserve_length=False, entity_anon_methods={}, entity_pseudo_formats={}):
880
+ """
881
+ Versione avanzata dell'anonimizzazione che supporta più parametri
882
+ """
883
+ if not entities:
884
+ return text
885
+
886
+ # Ordina le entità per posizione (dall'ultima alla prima) per non alterare gli indici
887
+ sorted_entities = sorted(entities, key=lambda x: x["start"], reverse=True)
888
+ anonymized = text
889
+
890
+ # Dizionario per tenere traccia dei valori sostituiti
891
+ pseudonyms = {}
892
+ type_counts = {}
893
+
894
+ for entity in sorted_entities:
895
+ entity_type = entity["entity_type"]
896
+ entity_text = entity["text"]
897
+ entity_start = entity["start"]
898
+ entity_end = entity["end"]
899
+
900
+ # Determina il metodo di anonimizzazione per questa entità specifica
901
+ anon_type = entity_anon_methods.get(entity_type, global_anon_type)
902
+
903
+ if anon_type == "replace":
904
+ # Formatta il tag in base al formato scelto
905
+ if tag_format == "<TAG>":
906
+ new_value = f"<{entity_type}>"
907
+ elif tag_format == "[TAG]":
908
+ new_value = f"[{entity_type}]"
909
+ elif tag_format == "{TAG}":
910
+ new_value = f"{{{entity_type}}}"
911
+ elif tag_format == "TAG_":
912
+ new_value = f"{entity_type}_"
913
+ else:
914
+ new_value = f"<{entity_type}>"
915
+
916
+ elif anon_type == "redact":
917
+ # Redact con il carattere scelto, mantenendo o meno la lunghezza originale
918
+ if preserve_length:
919
+ new_value = redact_char * (entity_end - entity_start)
920
+ else:
921
+ new_value = redact_char * 5 # Lunghezza fissa
922
+
923
+ elif anon_type == "pseudonymize":
924
+ # Pseudonimizzazione con nomi fittizi
925
+ if entity_text in pseudonyms:
926
+ new_value = pseudonyms[entity_text]
927
+ else:
928
+ # Inizializza il contatore se non esiste
929
+ if entity_type not in type_counts:
930
+ type_counts[entity_type] = 0
931
+
932
+ # Incrementa il contatore
933
+ type_counts[entity_type] += 1
934
+
935
+ # Ottieni il formato del pseudonimo per questo tipo di entità
936
+ pseudo_format = entity_pseudo_formats.get(entity_type, "")
937
+
938
+ # Genera un valore fittizio basato sul tipo e formato
939
+ if pseudo_format:
940
+ try:
941
+ # Prova a formattare usando il formato specificato
942
+ new_value = pseudo_format.format(
943
+ num=type_counts[entity_type],
944
+ type=entity_type,
945
+ orig=entity_text[:1] if entity_text else "X"
946
+ )
947
+ except Exception:
948
+ # Fallback in caso di errore di formattazione
949
+ new_value = f"{entity_type}{type_counts[entity_type]}"
950
+ else:
951
+ # Formati predefiniti per ogni tipo se non specificato
952
+ if entity_type == "PERSON":
953
+ new_value = f"Persona{type_counts[entity_type]}"
954
+ elif entity_type == "LOCATION":
955
+ new_value = f"Luogo{type_counts[entity_type]}"
956
+ elif entity_type == "ORGANIZATION":
957
+ new_value = f"Organizzazione{type_counts[entity_type]}"
958
+ elif entity_type == "DATE_TIME":
959
+ new_value = f"Data{type_counts[entity_type]}"
960
+ elif entity_type == "PHONE_NUMBER":
961
+ new_value = f"+39-XXX-XXX-{1000+type_counts[entity_type]}"
962
+ elif entity_type == "EMAIL_ADDRESS" or entity_type == "EMAIL":
963
+ new_value = f"email{type_counts[entity_type]}@esempio.com"
964
+ elif entity_type == "IBAN_CODE":
965
+ new_value = f"IT00X0000000000000{type_counts[entity_type]}"
966
+ elif entity_type == "CODICE_FISCALE" or entity_type == "CF":
967
+ new_value = f"ABCDEF00G00H000{type_counts[entity_type]}"
968
+ elif entity_type == "PARTITA_IVA" or entity_type == "PIVA":
969
+ new_value = f"IT0000000000{type_counts[entity_type]}"
970
+ elif entity_type == "TARGA":
971
+ new_value = f"XX000{type_counts[entity_type]}"
972
+ else:
973
+ new_value = f"{entity_type}{type_counts[entity_type]}"
974
+
975
+ # Memorizza il valore per riusi futuri
976
+ pseudonyms[entity_text] = new_value
977
+
978
+ # Adatta la lunghezza se necessario
979
+ if preserve_length and len(new_value) < (entity_end - entity_start):
980
+ new_value = new_value.ljust(entity_end - entity_start)
981
+ elif preserve_length and len(new_value) > (entity_end - entity_start):
982
+ # Troncamento con ellipsis
983
+ new_value = new_value[:entity_end - entity_start - 1] + "…"
984
+
985
+ else:
986
+ # Tipo sconosciuto, usa il metodo replace come fallback
987
+ new_value = f"<{entity_type}>"
988
+
989
+ # Sostituisci il testo
990
+ anonymized = anonymized[:entity_start] + new_value + anonymized[entity_end:]
991
+
992
+ return anonymized
993
+
994
+ # Esempi per la nuova interfaccia
995
+ entity_control_examples = [
996
+ [
997
+ "Il signor Mario Rossi, nato il 15/04/1980, CF: RSSMRC80D15H501V, residente in Via Roma 123, Milano, possiede la partita IVA IT12345678901.",
998
+ "replace",
999
+ True,
1000
+ False,
1001
+ 0.5,
1002
+ True, # person_enabled
1003
+ True, # location_enabled
1004
+ True, # organization_enabled
1005
+ True, # date_time_enabled
1006
+ True, # phone_number_enabled
1007
+ True, # email_enabled
1008
+ True, # iban_enabled
1009
+ True, # codice_fiscale_enabled
1010
+ True, # partita_iva_enabled
1011
+ True, # targa_enabled
1012
+ ],
1013
+ [
1014
+ "Per contattare il cliente Giovanni Bianchi utilizzare l'email [email protected] o il numero +39 333-123-4567.",
1015
+ "replace",
1016
+ False,
1017
+ True,
1018
+ 0.6,
1019
+ True, # person_enabled
1020
+ False, # location_enabled
1021
+ False, # organization_enabled
1022
+ False, # date_time_enabled
1023
+ True, # phone_number_enabled
1024
+ True, # email_enabled
1025
+ False, # iban_enabled
1026
+ False, # codice_fiscale_enabled
1027
+ False, # partita_iva_enabled
1028
+ False, # targa_enabled
1029
+ ],
1030
+ [
1031
+ "Il veicolo targato AB123CD appartiene a Maria Verdi, titolare del conto bancario IT12K1234567890123456789012.",
1032
+ "replace",
1033
+ True,
1034
+ True,
1035
+ 0.7,
1036
+ True, # person_enabled
1037
+ False, # location_enabled
1038
+ False, # organization_enabled
1039
+ False, # date_time_enabled
1040
+ False, # phone_number_enabled
1041
+ False, # email_enabled
1042
+ True, # iban_enabled
1043
+ False, # codice_fiscale_enabled
1044
+ False, # partita_iva_enabled
1045
+ True, # targa_enabled
1046
+ ]
1047
+ ]
1048
+
1049
+ def process_text_with_entity_control_wrapper(
1050
+ text,
1051
+ anonymization_type,
1052
+ use_stanford,
1053
+ use_regex,
1054
+ confidence_threshold,
1055
+ person_enabled,
1056
+ location_enabled,
1057
+ organization_enabled,
1058
+ date_time_enabled,
1059
+ phone_number_enabled,
1060
+ email_enabled,
1061
+ iban_enabled,
1062
+ codice_fiscale_enabled,
1063
+ partita_iva_enabled,
1064
+ targa_enabled
1065
+ ):
1066
+ """
1067
+ Funzione wrapper che passa i parametri predefiniti ai nuovi parametri della funzione originale
1068
+ """
1069
+ return process_text_with_entity_control(
1070
+ text=text,
1071
+ anonymization_type=anonymization_type,
1072
+ use_stanford=use_stanford,
1073
+ use_regex=use_regex,
1074
+ confidence_threshold=confidence_threshold,
1075
+ person_enabled=person_enabled,
1076
+ location_enabled=location_enabled,
1077
+ organization_enabled=organization_enabled,
1078
+ date_time_enabled=date_time_enabled,
1079
+ phone_number_enabled=phone_number_enabled,
1080
+ email_enabled=email_enabled,
1081
+ iban_enabled=iban_enabled,
1082
+ codice_fiscale_enabled=codice_fiscale_enabled,
1083
+ partita_iva_enabled=partita_iva_enabled,
1084
+ targa_enabled=targa_enabled,
1085
+ # Valori predefiniti per i nuovi parametri
1086
+ tag_format="<TAG>",
1087
+ redact_char="*",
1088
+ preserve_length=False,
1089
+ # Metodi specifici per tipo (tutti None = usa metodo globale)
1090
+ person_anon_method=None,
1091
+ location_anon_method=None,
1092
+ organization_anon_method=None,
1093
+ date_time_anon_method=None,
1094
+ phone_anon_method=None,
1095
+ email_anon_method=None,
1096
+ iban_anon_method=None,
1097
+ cf_anon_method=None,
1098
+ piva_anon_method=None,
1099
+ targa_anon_method=None,
1100
+ # Soglie specifiche (tutte None = usa soglia globale)
1101
+ person_threshold=None,
1102
+ location_threshold=None,
1103
+ organization_threshold=None,
1104
+ date_time_threshold=None,
1105
+ phone_threshold=None,
1106
+ email_threshold=None,
1107
+ iban_threshold=None,
1108
+ cf_threshold=None,
1109
+ piva_threshold=None,
1110
+ targa_threshold=None,
1111
+ # Formati predefiniti per i pseudonimi
1112
+ person_pseudo_format="Persona{num}",
1113
+ location_pseudo_format="Luogo{num}",
1114
+ organization_pseudo_format="Organizzazione{num}",
1115
+ date_pseudo_format="Data{num}",
1116
+ phone_pseudo_format="+39-XXX-XXX-{num}",
1117
+ email_pseudo_format="email{num}@esempio.com",
1118
+ iban_pseudo_format="IT00X0000000000000{num}",
1119
+ cf_pseudo_format="ABCDEF00G00H000{num}",
1120
+ piva_pseudo_format="IT0000000000{num}",
1121
+ targa_pseudo_format="XX000{num}"
1122
+ )
1123
+
1124
+ # Crea l'interfaccia Gradio con controllo entità
1125
+ demo_advanced = gr.Interface(
1126
+ fn=process_text_with_entity_control_wrapper, # Usa la funzione wrapper
1127
+ inputs=[
1128
+ gr.Textbox(
1129
+ label="Testo da analizzare",
1130
+ lines=5,
1131
+ placeholder="Inserisci il testo contenente dati sensibili...",
1132
+ value="Il signor Marco Rossi, nato il 15/04/1978, CF: RSSMRC78D15H501T, può essere contattato al numero +39 333-1234567 o all'email [email protected]."
1133
+ ),
1134
+ gr.Radio(
1135
+ ["replace", "redact", "pseudonymize"],
1136
+ label="Tipo di anonimizzazione",
1137
+ value="replace"
1138
+ ),
1139
+ gr.Checkbox(
1140
+ label="Usa modello Stanford",
1141
+ value=True
1142
+ ),
1143
+ gr.Checkbox(
1144
+ label="Usa Regex Fallback",
1145
+ value=True
1146
+ ),
1147
+ gr.Slider(
1148
+ minimum=0.1,
1149
+ maximum=1.0,
1150
+ value=0.5,
1151
+ step=0.05,
1152
+ label="Soglia di confidenza minima"
1153
+ ),
1154
+ # Controlli per i tipi di entità
1155
+ gr.Checkbox(label="Persone (PERSON)", value=True),
1156
+ gr.Checkbox(label="Luoghi (LOCATION)", value=True),
1157
+ gr.Checkbox(label="Organizzazioni (ORGANIZATION)", value=True),
1158
+ gr.Checkbox(label="Date (DATE_TIME)", value=True),
1159
+ gr.Checkbox(label="Numeri di telefono (PHONE_NUMBER)", value=True),
1160
+ gr.Checkbox(label="Email (EMAIL)", value=True),
1161
+ gr.Checkbox(label="IBAN (IBAN_CODE)", value=True),
1162
+ gr.Checkbox(label="Codici Fiscali (CODICE_FISCALE)", value=True),
1163
+ gr.Checkbox(label="Partite IVA (PARTITA_IVA)", value=True),
1164
+ gr.Checkbox(label="Targhe (TARGA)", value=True)
1165
+ ],
1166
+ outputs=[
1167
+ gr.HTML(label="Testo con entità evidenziate"),
1168
+ gr.Textbox(label="Testo anonimizzato", lines=5),
1169
+ gr.Markdown(label="Statistiche di rilevamento")
1170
+ ],
1171
+ title="🔒 Sistema Ibrido di Anonimizzazione Dati - Controllo Entità",
1172
+ description="Analizza e anonimizza testi selezionando i tipi di entità da processare.\n"
1173
+ "I diversi colori indicano i tipi di entità rilevate.",
1174
+ examples=entity_control_examples,
1175
+ theme=gr.themes.Soft(),
1176
+ allow_flagging="never"
1177
+ )
1178
+
1179
+ # Avvia l'interfaccia migliorata
1180
+ # demo_advanced.launch(share=True, debug=True)
1181
+
1182
+ # =========================================================
1183
+ # CELLA 10: INTERFACCIA AVANZATA CON PARAMETRI DI ANONIMIZZAZIONE
1184
+ # =========================================================
1185
+
1186
+ with gr.Blocks(theme=gr.themes.Soft()) as demo_blocks:
1187
+ gr.Markdown("# 🔒 Sistema Ibrido di Anonimizzazione Dati")
1188
+ gr.Markdown("Analizza e anonimizza testi in italiano con controllo avanzato dei parametri.")
1189
+
1190
+ with gr.Row():
1191
+ with gr.Column(scale=2):
1192
+ text_input = gr.Textbox(
1193
+ label="Testo da analizzare",
1194
+ lines=6,
1195
+ placeholder="Inserisci il testo contenente dati sensibili...",
1196
+ value="Il signor Marco Rossi, nato il 15/04/1978, CF: RSSMRC78D15H501T, può essere contattato al numero +39 333-1234567 o all'email [email protected]."
1197
+ )
1198
+
1199
+ with gr.Tabs():
1200
+ with gr.TabItem("Impostazioni Base"):
1201
+ with gr.Row():
1202
+ with gr.Column():
1203
+ anon_type = gr.Radio(
1204
+ ["replace", "redact", "pseudonymize"],
1205
+ label="Tipo di anonimizzazione globale",
1206
+ value="replace"
1207
+ )
1208
+ confidence = gr.Slider(
1209
+ minimum=0.1,
1210
+ maximum=1.0,
1211
+ value=0.5,
1212
+ step=0.05,
1213
+ label="Soglia di confidenza globale"
1214
+ )
1215
+
1216
+ with gr.Column():
1217
+ use_stanford = gr.Checkbox(label="Usa modello Stanford", value=True)
1218
+ use_regex = gr.Checkbox(label="Usa Regex Fallback", value=True)
1219
+
1220
+ with gr.TabItem("Parametri di Anonimizzazione"):
1221
+ with gr.Row():
1222
+ with gr.Column():
1223
+ tag_format = gr.Radio(
1224
+ ["<TAG>", "[TAG]", "{TAG}", "TAG_"],
1225
+ label="Formato dei tag di sostituzione",
1226
+ value="<TAG>"
1227
+ )
1228
+
1229
+ redact_char = gr.Radio(
1230
+ ["*", "X", "#", "_"],
1231
+ label="Carattere di oscuramento (per redact)",
1232
+ value="*"
1233
+ )
1234
+
1235
+ with gr.Column():
1236
+ preserve_length = gr.Checkbox(
1237
+ label="Preserva lunghezza originale nelle sostituzioni",
1238
+ value=False
1239
+ )
1240
+
1241
+ gr.Markdown("### Anteprima formati di tag")
1242
+ anteprima_html = gr.HTML(value="<div style='padding: 10px; background-color: #f0f0f0; border-radius: 5px;'><p><b>Replace:</b> &lt;PERSON&gt;, [PERSON], {PERSON}, PERSON_</p><p><b>Redact:</b> *****, XXXXX, #####, _____</p><p><b>Pseudonymize:</b> Persona1, Luogo1, Data1...</p></div>")
1243
+
1244
+ with gr.Accordion("Metodi specifici per tipo di entità", open=False):
1245
+ gr.Markdown("Seleziona un metodo specifico per ogni tipo di entità, o lascia 'Globale' per usare il metodo globale")
1246
+
1247
+ with gr.Row():
1248
+ with gr.Column():
1249
+ person_method = gr.Dropdown(
1250
+ [None, "replace", "redact", "pseudonymize"],
1251
+ label="Metodo per PERSON",
1252
+ value=None
1253
+ )
1254
+ location_method = gr.Dropdown(
1255
+ [None, "replace", "redact", "pseudonymize"],
1256
+ label="Metodo per LOCATION",
1257
+ value=None
1258
+ )
1259
+ organization_method = gr.Dropdown(
1260
+ [None, "replace", "redact", "pseudonymize"],
1261
+ label="Metodo per ORGANIZATION",
1262
+ value=None
1263
+ )
1264
+ date_method = gr.Dropdown(
1265
+ [None, "replace", "redact", "pseudonymize"],
1266
+ label="Metodo per DATE_TIME",
1267
+ value=None
1268
+ )
1269
+ phone_method = gr.Dropdown(
1270
+ [None, "replace", "redact", "pseudonymize"],
1271
+ label="Metodo per PHONE_NUMBER",
1272
+ value=None
1273
+ )
1274
+
1275
+ with gr.Column():
1276
+ email_method = gr.Dropdown(
1277
+ [None, "replace", "redact", "pseudonymize"],
1278
+ label="Metodo per EMAIL",
1279
+ value=None
1280
+ )
1281
+ iban_method = gr.Dropdown(
1282
+ [None, "replace", "redact", "pseudonymize"],
1283
+ label="Metodo per IBAN_CODE",
1284
+ value=None
1285
+ )
1286
+ cf_method = gr.Dropdown(
1287
+ [None, "replace", "redact", "pseudonymize"],
1288
+ label="Metodo per CODICE_FISCALE",
1289
+ value=None
1290
+ )
1291
+ piva_method = gr.Dropdown(
1292
+ [None, "replace", "redact", "pseudonymize"],
1293
+ label="Metodo per PARTITA_IVA",
1294
+ value=None
1295
+ )
1296
+ targa_method = gr.Dropdown(
1297
+ [None, "replace", "redact", "pseudonymize"],
1298
+ label="Metodo per TARGA",
1299
+ value=None
1300
+ )
1301
+
1302
+ with gr.TabItem("Soglie di Confidenza"):
1303
+ gr.Markdown("### Imposta soglie di confidenza specifiche per tipo di entità")
1304
+ gr.Markdown("Lascia vuoto per usare la soglia globale")
1305
+
1306
+ with gr.Row():
1307
+ with gr.Column():
1308
+ person_threshold = gr.Slider(
1309
+ minimum=0.1,
1310
+ maximum=1.0,
1311
+ step=0.05,
1312
+ label="Soglia per PERSON",
1313
+ value=None
1314
+ )
1315
+ location_threshold = gr.Slider(
1316
+ minimum=0.1,
1317
+ maximum=1.0,
1318
+ step=0.05,
1319
+ label="Soglia per LOCATION",
1320
+ value=None
1321
+ )
1322
+ organization_threshold = gr.Slider(
1323
+ minimum=0.1,
1324
+ maximum=1.0,
1325
+ step=0.05,
1326
+ label="Soglia per ORGANIZATION",
1327
+ value=None
1328
+ )
1329
+ date_threshold = gr.Slider(
1330
+ minimum=0.1,
1331
+ maximum=1.0,
1332
+ step=0.05,
1333
+ label="Soglia per DATE_TIME",
1334
+ value=None
1335
+ )
1336
+ phone_threshold = gr.Slider(
1337
+ minimum=0.1,
1338
+ maximum=1.0,
1339
+ step=0.05,
1340
+ label="Soglia per PHONE_NUMBER",
1341
+ value=None
1342
+ )
1343
+
1344
+ with gr.Column():
1345
+ email_threshold = gr.Slider(
1346
+ minimum=0.1,
1347
+ maximum=1.0,
1348
+ step=0.05,
1349
+ label="Soglia per EMAIL",
1350
+ value=None
1351
+ )
1352
+ iban_threshold = gr.Slider(
1353
+ minimum=0.1,
1354
+ maximum=1.0,
1355
+ step=0.05,
1356
+ label="Soglia per IBAN_CODE",
1357
+ value=None
1358
+ )
1359
+ cf_threshold = gr.Slider(
1360
+ minimum=0.1,
1361
+ maximum=1.0,
1362
+ step=0.05,
1363
+ label="Soglia per CODICE_FISCALE",
1364
+ value=None
1365
+ )
1366
+ piva_threshold = gr.Slider(
1367
+ minimum=0.1,
1368
+ maximum=1.0,
1369
+ step=0.05,
1370
+ label="Soglia per PARTITA_IVA",
1371
+ value=None
1372
+ )
1373
+ targa_threshold = gr.Slider(
1374
+ minimum=0.1,
1375
+ maximum=1.0,
1376
+ step=0.05,
1377
+ label="Soglia per TARGA",
1378
+ value=None
1379
+ )
1380
+
1381
+ with gr.TabItem("Formati Pseudonimi"):
1382
+ gr.Markdown("### Personalizza i formati dei pseudonimi")
1383
+ gr.Markdown("Usa {num} per inserire il numero progressivo, {type} per il tipo di entità, {orig} per l'iniziale dell'originale")
1384
+
1385
+ with gr.Row():
1386
+ with gr.Column():
1387
+ person_format = gr.Textbox(
1388
+ label="Formato per PERSON",
1389
+ value="Persona{num}",
1390
+ placeholder="es. Persona{num}, P{num}, {orig}..."
1391
+ )
1392
+ location_format = gr.Textbox(
1393
+ label="Formato per LOCATION",
1394
+ value="Luogo{num}",
1395
+ placeholder="es. Luogo{num}, L{num}..."
1396
+ )
1397
+ organization_format = gr.Textbox(
1398
+ label="Formato per ORGANIZATION",
1399
+ value="Organizzazione{num}",
1400
+ placeholder="es. Org{num}, Azienda{num}..."
1401
+ )
1402
+ date_format = gr.Textbox(
1403
+ label="Formato per DATE_TIME",
1404
+ value="Data{num}",
1405
+ placeholder="es. GG/MM/AAAA, Data{num}..."
1406
+ )
1407
+ phone_format = gr.Textbox(
1408
+ label="Formato per PHONE_NUMBER",
1409
+ value="+39-XXX-XXX-{num}",
1410
+ placeholder="es. +39-XXX-XXX-{num}..."
1411
+ )
1412
+
1413
+ with gr.Column():
1414
+ email_format = gr.Textbox(
1415
+ label="Formato per EMAIL",
1416
+ value="email{num}@esempio.com",
1417
+ placeholder="es. user{num}@domain.com..."
1418
+ )
1419
+ iban_format = gr.Textbox(
1420
+ label="Formato per IBAN_CODE",
1421
+ value="IT00X0000000000000{num}",
1422
+ placeholder="es. IT00X0000..."
1423
+ )
1424
+ cf_format = gr.Textbox(
1425
+ label="Formato per CODICE_FISCALE",
1426
+ value="ABCDEF00G00H000{num}",
1427
+ placeholder="es. ABCDEF00G00H000{num}..."
1428
+ )
1429
+ piva_format = gr.Textbox(
1430
+ label="Formato per PARTITA_IVA",
1431
+ value="IT0000000000{num}",
1432
+ placeholder="es. IT0000000000{num}..."
1433
+ )
1434
+ targa_format = gr.Textbox(
1435
+ label="Formato per TARGA",
1436
+ value="XX000{num}",
1437
+ placeholder="es. XX000{num}..."
1438
+ )
1439
+
1440
+ process_btn = gr.Button("Analizza e Anonimizza", variant="primary")
1441
+
1442
+ with gr.Column(scale=1):
1443
+ gr.Markdown("### Seleziona i tipi di entità da anonimizzare")
1444
+
1445
+ with gr.Group():
1446
+ person_enabled = gr.Checkbox(label="👤 Persone (PERSON)", value=True)
1447
+ location_enabled = gr.Checkbox(label="📍 Luoghi (LOCATION)", value=True)
1448
+ organization_enabled = gr.Checkbox(label="🏢 Organizzazioni (ORGANIZATION)", value=True)
1449
+ date_time_enabled = gr.Checkbox(label="📅 Date (DATE_TIME)", value=True)
1450
+ phone_number_enabled = gr.Checkbox(label="📞 Numeri di telefono (PHONE_NUMBER)", value=True)
1451
+ email_enabled = gr.Checkbox(label="📧 Email (EMAIL)", value=True)
1452
+ iban_enabled = gr.Checkbox(label="💳 IBAN (IBAN_CODE)", value=True)
1453
+ codice_fiscale_enabled = gr.Checkbox(label="🪪 Codici Fiscali (CODICE_FISCALE)", value=True)
1454
+ partita_iva_enabled = gr.Checkbox(label="🏷️ Partite IVA (PARTITA_IVA)", value=True)
1455
+ targa_enabled = gr.Checkbox(label="🚗 Targhe (TARGA)", value=True)
1456
+
1457
+ with gr.Row():
1458
+ select_all_btn = gr.Button("Seleziona tutti")
1459
+ clear_all_btn = gr.Button("Deseleziona tutti")
1460
+
1461
+ with gr.Accordion("Guida rapida", open=False):
1462
+ gr.Markdown("""
1463
+ **Tipi di anonimizzazione:**
1464
+ - **Replace**: sostituisce l'entità con un tag (es. <PERSON>)
1465
+ - **Redact**: oscura l'entità con caratteri (es. *****)
1466
+ - **Pseudonymize**: sostituisce con valori fittizi (es. Persona1)
1467
+
1468
+ **Formato tag:**
1469
+ - `<TAG>`: usa tag HTML (es. <PERSON>)
1470
+ - `[TAG]`: usa parentesi quadre (es. [PERSON])
1471
+ - `{TAG}`: usa parentesi graffe (es. {PERSON})
1472
+ - `TAG_`: usa underscore (es. PERSON_)
1473
+
1474
+ **Preserva lunghezza:**
1475
+ - Se attivo, mantiene la lunghezza originale dell'entità
1476
+ - Utile per mantenere il formato del documento
1477
+ """)
1478
+
1479
+ with gr.Tabs():
1480
+ with gr.TabItem("Risultati"):
1481
+ html_output = gr.HTML(label="Testo con entità evidenziate")
1482
+ anon_output = gr.Textbox(label="Testo anonimizzato", lines=5)
1483
+ stats_output = gr.Markdown(label="Statistiche di rilevamento")
1484
+
1485
+ # Funzione per aggiornare l'anteprima dei formati di tag
1486
+ def update_preview(tag_format, redact_char, preserve_length):
1487
+ replace_examples = {
1488
+ "<TAG>": "&lt;PERSON&gt;",
1489
+ "[TAG]": "[PERSON]",
1490
+ "{TAG}": "{PERSON}",
1491
+ "TAG_": "PERSON_"
1492
+ }
1493
+
1494
+ redact_example = redact_char * 5
1495
+ if preserve_length:
1496
+ redact_note = " (mantenendo lunghezza originale)"
1497
+ else:
1498
+ redact_note = " (lunghezza fissa)"
1499
+
1500
+ return f"""
1501
+ <div style='padding: 10px; background-color: #f0f0f0; border-radius: 5px;'>
1502
+ <p><b>Replace:</b> {replace_examples[tag_format]}</p>
1503
+ <p><b>Redact:</b> {redact_example}{redact_note}</p>
1504
+ <p><b>Pseudonymize:</b> Persona1, Luogo1, Data1...</p>
1505
+ </div>
1506
+ """
1507
+
1508
+ # Aggiorna l'anteprima quando cambiano i parametri
1509
+ tag_format.change(
1510
+ update_preview,
1511
+ inputs=[tag_format, redact_char, preserve_length],
1512
+ outputs=anteprima_html
1513
+ )
1514
+
1515
+ redact_char.change(
1516
+ update_preview,
1517
+ inputs=[tag_format, redact_char, preserve_length],
1518
+ outputs=anteprima_html
1519
+ )
1520
+
1521
+ preserve_length.change(
1522
+ update_preview,
1523
+ inputs=[tag_format, redact_char, preserve_length],
1524
+ outputs=anteprima_html
1525
+ )
1526
+
1527
+ # Logica per i pulsanti di selezione
1528
+ def select_all():
1529
+ return [True] * 10
1530
+
1531
+ def clear_all():
1532
+ return [False] * 10
1533
+
1534
+ select_all_btn.click(
1535
+ select_all,
1536
+ inputs=None,
1537
+ outputs=[
1538
+ person_enabled, location_enabled, organization_enabled, date_time_enabled,
1539
+ phone_number_enabled, email_enabled, iban_enabled, codice_fiscale_enabled,
1540
+ partita_iva_enabled, targa_enabled
1541
+ ]
1542
+ )
1543
+
1544
+ clear_all_btn.click(
1545
+ clear_all,
1546
+ inputs=None,
1547
+ outputs=[
1548
+ person_enabled, location_enabled, organization_enabled, date_time_enabled,
1549
+ phone_number_enabled, email_enabled, iban_enabled, codice_fiscale_enabled,
1550
+ partita_iva_enabled, targa_enabled
1551
+ ]
1552
+ )
1553
+
1554
+ # Callback per il pulsante di processo
1555
+ process_btn.click(
1556
+ process_text_with_entity_control,
1557
+ inputs=[
1558
+ text_input, anon_type, use_stanford, use_regex, confidence,
1559
+ person_enabled, location_enabled, organization_enabled, date_time_enabled,
1560
+ phone_number_enabled, email_enabled, iban_enabled, codice_fiscale_enabled,
1561
+ partita_iva_enabled, targa_enabled,
1562
+ # Parametri di anonimizzazione avanzati
1563
+ tag_format, redact_char, preserve_length,
1564
+ # Metodi specifici per tipo
1565
+ person_method, location_method, organization_method, date_method,
1566
+ phone_method, email_method, iban_method, cf_method, piva_method, targa_method,
1567
+ # Soglie specifiche per tipo
1568
+ person_threshold, location_threshold, organization_threshold, date_threshold,
1569
+ phone_threshold, email_threshold, iban_threshold, cf_threshold, piva_threshold, targa_threshold,
1570
+ # Formati dei pseudonimi
1571
+ person_format, location_format, organization_format, date_format, phone_format,
1572
+ email_format, iban_format, cf_format, piva_format, targa_format
1573
+ ],
1574
+ outputs=[html_output, anon_output, stats_output]
1575
+ )
1576
+
1577
+ # Avvia l'interfaccia a blocchi (commenta la linea launch della cella 11 se la usi)
1578
+ if __name__ == "__main__":
1579
+ demo_blocks.launch(
1580
+ server_name="0.0.0.0",
1581
+ server_port=7860,
1582
+ share=False,
1583
+ show_error=True
1584
+ )