AlessandroAlfieri commited on
Commit
4e96b7a
·
verified ·
1 Parent(s): cf85ea8

creazione progetto

Browse files
.env ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ AZURE_ENDPOINT_EMB="https://pr-giorno6-embeddings-resource.cognitiveservices.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-05-15"
2
+ AZURE_API_KEY_EMB="k9qAhDsM91sdoW79twYQCEqAjIPjGGV7JxSEztsGid3EqQktnEsCJQQJ99BFACHrzpqXJ3w3AAAAACOGNMeC"
3
+ AZURE_ENDPOINT="https://pr-giorno6-rag-resource.cognitiveservices.azure.com/"
4
+ AZURE_API_KEY="3MwIo1ZmFJvm1bEY5dW8AlJB3rfard2ux1oH6JoShy76Uo1q34ZMJQQJ99BFACHrzpqXJ3w3AAAAACOGLOIY"
CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ ## [Documentato] - 2025-06-30
4
+
5
+ ### Added
6
+ - Documentazione tecnica completa
7
+ - Analisi automatizzata del progetto
8
+ - Template per future implementazioni
9
+
10
+ ### Documentation
11
+ - README.md principale
12
+ - Documentazione tecnica in docs/
13
+ - Guide di architettura e utilizzo
14
+
15
+ *Changelog generato automaticamente*
README.md CHANGED
@@ -1,10 +1,205 @@
1
- ---
2
- title: Progetto Hackathon
3
- emoji: 🦀
4
- colorFrom: red
5
- colorTo: purple
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔒 Anonimizzatore Documenti con AI
2
+
3
+ Sistema completo per anonimizzazione e analisi intelligente di documenti testuali con protezione privacy GDPR.
4
+
5
+ ## 🚀 Funzionalità
6
+
7
+ - **🔐 Anonimizzazione Automatica**: NER + Regex per proteggere dati sensibili
8
+ - **💬 RAG Chatbot**: Chat intelligente sui documenti anonimizzati
9
+ - **🤖 Multi-Agent AI**: 4 agenti CrewAI per analisi approfondite
10
+ - **📊 Dashboard Web**: Interfaccia Streamlit completa
11
+ - **📥 Export Risultati**: Download JSON strutturati
12
+
13
+ ## 📋 Requisiti
14
+
15
+ - Python 3.8+
16
+ - Account Azure OpenAI
17
+ - Dipendenze in `requirements.txt`
18
+
19
+ ## ⚙️ Installazione
20
+
21
+ 1. **Clona il repository**
22
+ ```bash
23
+ git clone <repo-url>
24
+ cd document_anonymizer
25
+ ```
26
+
27
+ 2. **Installa dipendenze**
28
+ ```bash
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ 3. **Configura variabili d'ambiente**
33
+ ```bash
34
+ cp .env.example .env
35
+ ```
36
+
37
+ Modifica `.env` con le tue credenziali Azure:
38
+ ```
39
+ AZURE_ENDPOINT=https://your-resource.openai.azure.com/
40
+ AZURE_API_KEY=your-api-key
41
+ AZURE_ENDPOINT_EMB=https://your-embedding-resource.openai.azure.com/
42
+ AZURE_API_KEY_EMB=your-embedding-api-key
43
+ ```
44
+
45
+ 4. **Avvia l'applicazione**
46
+ ```bash
47
+ streamlit run main.py
48
+ ```
49
+
50
+ ## 🎯 Come Usare
51
+
52
+ ### 1. Upload Documenti
53
+ - Carica file `.txt` nella tab "Upload"
54
+ - Supporta upload multipli
55
+
56
+ ### 2. Anonimizzazione
57
+ - Vai alla tab "Anonimizzazione"
58
+ - Clicca "Avvia Anonimizzazione"
59
+ - Revisiona e modifica entità rilevate
60
+ - Conferma i documenti
61
+
62
+ ### 3. Analisi AI
63
+ - Tab "Analisi": Analisi Azure OpenAI per singoli documenti
64
+ - Tab "RAG": Chat interattiva con i documenti
65
+ - Tab "CrewAI": Analisi multi-agente avanzate
66
+
67
+ ## 🤖 Agenti CrewAI
68
+
69
+ - **📄 Document Analyst**: Classificazione e analisi strutturale
70
+ - **😊 Sentiment Analyst**: Analisi emozioni e trend
71
+ - **🎯 Strategy Coordinator**: Sintesi executive e raccomandazioni
72
+
73
+ ## 📁 Struttura Progetto
74
+
75
+ ```
76
+ document_anonymizer/
77
+ ├── main.py # App Streamlit principale
78
+ ├── config.py # Configurazioni sistema
79
+ ├── anonymizer.py # Sistema anonimizzazione NER+Regex
80
+ ├── ai_processor.py # Azure + RAG + CrewAI
81
+ ├── ui_components.py # Componenti UI riutilizzabili
82
+ ├── utils.py # Funzioni utility
83
+ ├── requirements.txt # Dipendenze Python
84
+ ├── .env.example # Template environment
85
+ └── README.md # Questa documentazione
86
+ ```
87
+
88
+ ## 🔐 Privacy & Sicurezza
89
+
90
+ - **Privacy by Design**: Anonimizzazione prima di qualsiasi elaborazione AI
91
+ - **GDPR Compliant**: Nessun dato sensibile inviato ai modelli
92
+ - **Controllo Manuale**: Revisione ed editing delle entità rilevate
93
+ - **Tracciabilità**: Cronologia completa delle operazioni
94
+
95
+ ## 🛠️ Entità Supportate
96
+
97
+ ### Regex Pattern
98
+ - **IBAN**: Codici bancari italiani
99
+ - **EMAIL**: Indirizzi email
100
+ - **CF**: Codici fiscali italiani
101
+ - **CARD**: Numeri carte di credito
102
+ - **PHONE**: Numeri di telefono
103
+
104
+ ### NER (Named Entity Recognition)
105
+ - **PER**: Nomi di persone
106
+ - **ORG**: Organizzazioni
107
+ - **LOC**: Luoghi
108
+ - **MISC**: Entità varie
109
+
110
+ ## 📊 Tipi di Analisi CrewAI
111
+
112
+ ### 🔍 Comprensiva
113
+ Analisi completa con tutti e 4 gli agenti per insights 360°
114
+
115
+ ### 📄 Documentale
116
+ Focus su classificazione, struttura e organizzazione documenti
117
+
118
+ ### 😊 Sentiment
119
+ Analisi emozioni, soddisfazione e trend comunicazioni
120
+
121
+ ### 🔍 RAG Avanzata
122
+ Query complesse con recupero semantico e correlazioni
123
+
124
+ ### ⚙️ Personalizzata
125
+ Selezione manuale agenti per analisi su misura
126
+
127
+ ## 🔧 Configurazione Avanzata
128
+
129
+ ### Modelli Azure
130
+ Modifica in `config.py`:
131
+ ```python
132
+ DEPLOYMENT_NAME = "gpt-4o" # Tuo deployment chat
133
+ AZURE_EMBEDDING_DEPLOYMENT_NAME = "text-embedding-ada-002" # Tuo deployment embedding
134
+ ```
135
+
136
+ ### Pattern Regex Personalizzati
137
+ Aggiungi in `config.py`:
138
+ ```python
139
+ REGEX_PATTERNS = {
140
+ # Pattern esistenti...
141
+ "CUSTOM_PATTERN": r'your_regex_here'
142
+ }
143
+ ```
144
+
145
+ ## 🐛 Troubleshooting
146
+
147
+ ### Errore Azure OpenAI
148
+ - Verifica credenziali in `.env`
149
+ - Controlla deployment names
150
+ - Verifica quota e limiti Azure
151
+
152
+ ### Errore NER Model
153
+ - Controlla connessione internet
154
+ - Aumenta timeout download modello
155
+ - Usa cache Hugging Face
156
+
157
+ ### Performance Lente
158
+ - Riduci dimensione documenti
159
+ - Usa meno chunks per RAG
160
+ - Ottimizza parametri CrewAI
161
+
162
+ ## 📈 Esempi Query
163
+
164
+ ### Business Intelligence
165
+ ```
166
+ "Analizza i temi principali nei documenti e identifica possibili rischi operativi"
167
+ ```
168
+
169
+ ### Customer Service
170
+ ```
171
+ "Valuta il sentiment nelle comunicazioni clienti e suggerisci miglioramenti"
172
+ ```
173
+
174
+ ### Compliance
175
+ ```
176
+ "Verifica la conformità delle comunicazioni e identifica potenziali problemi legali"
177
+ ```
178
+
179
+ ### Strategic Analysis
180
+ ```
181
+ "Fornisci un'analisi comprensiva con raccomandazioni strategiche actionable"
182
+ ```
183
+
184
+ ## 🤝 Contributi
185
+
186
+ 1. Fork il progetto
187
+ 2. Crea feature branch (`git checkout -b feature/AmazingFeature`)
188
+ 3. Commit modifiche (`git commit -m 'Add AmazingFeature'`)
189
+ 4. Push al branch (`git push origin feature/AmazingFeature`)
190
+ 5. Apri Pull Request
191
+
192
+ ## 📄 Licenza
193
+
194
+ Distribuito sotto licenza MIT. Vedi `LICENSE` per maggiori informazioni.
195
+
196
+ ## 📞 Supporto
197
+
198
+ Per supporto e domande:
199
+ - Apri una Issue su GitHub
200
+ - Contatta il team di sviluppo
201
+ - Consulta la documentazione Azure OpenAI
202
+
203
+ ---
204
+
205
+ **⚡ Quick Start**: `pip install -r requirements.txt && streamlit run main.py`
data/Mail.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Da: mario.rossi85@email.com
2
+ A: supporto@smartdocs.srl
3
+ Data: 20 Giugno 2025, 12:30
4
+ Oggetto: Richiesta Cambio IBAN e Chiarimenti Fattura N. 734/2025
5
+
6
+ Spett.le SmartDocs Srl,
7
+
8
+ Mi chiamo Mario Rossi e vi contatto in qualità di vostro cliente con Codice Fiscale RSSMRA85M01H501Z.
9
+
10
+ Vi scrivo in merito all'ultima fattura, la N. 734/2025, che presenta un importo che non mi torna. Vorrei ricevere dei chiarimenti in merito.
11
+
12
+ Cogliendo l'occasione, vorrei anche aggiornare le mie coordinate bancarie per la domiciliazione dei pagamenti futuri. Il mio attuale indirizzo di fatturazione, che vi prego di non modificare, è Via Garibaldi 10, 20121 Milano (MI).
13
+
14
+ Il vecchio IBAN da disattivare è IT60X0542811101000000123456.
15
+
16
+ Vi prego di addebitare le prossime fatture sul mio nuovo conto corrente, il cui IBAN è IT12Y0306909606100000012345.
17
+
18
+ Per qualsiasi necessità o per fornirmi i chiarimenti richiesti, potete contattarmi telefonicamente al numero 333 1234567.
19
+
20
+ In attesa di un vostro gentile riscontro, porgo cordiali saluti.
21
+
22
+ Distinti saluti,
23
+ Mario Rossi
data/contratto1.txt ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DISDETTA CONTRATTO - RACCOMANDATA A/R
2
+
3
+ Spett.le TELECOM PLUS S.P.A.
4
+ Servizio Clienti
5
+ Via delle Telecomunicazioni 100
6
+ 00100 Roma
7
+
8
+ Milano, 27 giugno 2025
9
+
10
+ Oggetto: DISDETTA IMMEDIATA contratto telefonico n. TC789456123 - SERVIZIO PESSIMO
11
+
12
+ Con la presente comunico la DISDETTA IMMEDIATA del contratto di telefonia mobile intestato a:
13
+ Nome: Roberto Neri
14
+ Codice Cliente: 7894561230
15
+ Numero: +39 335-1122334
16
+
17
+ MOTIVAZIONI della disdetta (gravi inadempienze contrattuali):
18
+
19
+ 1. COPERTURA INESISTENTE: In 8 mesi di contratto, nella mia zona residenziale (Zona San Siro, Milano) il segnale è SEMPRE assente o insufficiente. Chiamate che cadono continuamente, internet inutilizzabile.
20
+
21
+ 2. FATTURAZIONI ERRATE: Ho ricevuto 3 addebiti non dovuti per servizi mai richiesti (€ 45 totali) che, nonostante le segnalazioni, NON avete mai rimborsato.
22
+
23
+ 3. ASSISTENZA CLIENTI INADEGUATA: Tempo di attesa medio 45 minuti, operatori incompetenti che non risolvono mai i problemi. Ho perso GIORNI interi al telefono con voi.
24
+
25
+ 4. PROMESSE NON MANTENUTE: Mi avevate garantito "copertura ottimale" e "assistenza premium" - TUTTO FALSO!
26
+
27
+ Pretendo:
28
+ - Cessazione immediata del servizio senza penali (giusta causa ex art. 1456 C.C.)
29
+ - Rimborso degli addebiti non dovuti (€ 45)
30
+ - Rimborso quota parte ultimo mese non utilizzabile
31
+
32
+ Se non riceverò conferma entro 7 giorni, procederò per vie legali.
33
+
34
+ NON consiglierò MAI i vostri servizi a nessuno.
35
+
36
+ Distinti saluti,
37
+ Roberto Neri
data/contratto2.txt ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 🤝 ACCORDO DI PARTNERSHIP STRATEGICA 🤝
2
+
3
+ Tra le prestigiose aziende:
4
+
5
+ 🏢 INNOVATECH S.R.L.
6
+ Via Tecnologia 45 - 20099 Milano (MI)
7
+ P.IVA: 12345678901
8
+ Rappresentata dall'Amministratore Delegato Ing. Marco Bernardini
9
+
10
+ 🏢 FUTURE SOLUTIONS S.P.A.
11
+ Corso Europa 123 - 20100 Milano (MI)
12
+ P.IVA: 98765432109
13
+ Rappresentata dal Presidente Dott.ssa Anna Verdi
14
+
15
+ Si stipula il presente accordo di collaborazione con grande entusiasmo e fiducia reciproca!
16
+
17
+ 🎯 OBIETTIVI DELLA PARTNERSHIP:
18
+ - Sviluppo congiunto di soluzioni innovative per l'industria 4.0
19
+ - Condivisione know-how e best practices tecnologiche
20
+ - Espansione su mercati internazionali (Focus: Germania e Francia)
21
+ - Ricerca e sviluppo progetti all'avanguardia
22
+
23
+ 💰 ASPETTI ECONOMICI:
24
+ - Investimento comune: € 500.000 (50% ciascuna parte)
25
+ - Ripartizione ricavi: 60% InnovaTech - 40% Future Solutions
26
+ - Durata: 3 anni rinnovabili automaticamente
27
+ - Target fatturato congiunto primo anno: € 2.000.000
28
+
29
+ 🌟 VANTAGGI ESCLUSIVI:
30
+ ✅ Accesso reciproco a tecnologie proprietarie
31
+ ✅ Team di lavoro integrato (20 ingegneri top-level)
32
+ ✅ Sinergie commerciali e di marketing
33
+ ✅ Condivisione reti distributive europee
34
+ ✅ Supporto R&D per brevetti internazionali
35
+
36
+ 🎉 MILESTONE PREVISTE:
37
+ - Luglio 2025: Lancio primo prodotto congiunto
38
+ - Settembre 2025: Partecipazione fiera tecnologica Monaco
39
+ - Dicembre 2025: Apertura filiale comune Berlino
40
+ - Marzo 2026: Presentazione piattaforma AI proprietaria
41
+
42
+ Le parti si impegnano a collaborare con spirito costruttivo e trasparenza totale per il successo di questa entusiasmante avventura imprenditoriale!
43
+
44
+ Milano, 27 giugno 2025
45
+
46
+ Firma InnovaTech: _________________ Firma Future Solutions: _________________
47
+ (Ing. M. Bernardini) (Dott.ssa A. Verdi)
48
+
49
+ Testimoni:
50
+ Dr. Roberto Fiori _________________ Avv. Silvia Monti _________________
51
+
52
+ 🍾 Brindisi di celebrazione previsto per il 30/06/2025 ore 18:00! 🥂
data/contratto3.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CONTRATTO DI LOCAZIONE AD USO ABITATIVO
2
+
3
+ Tra:
4
+ LOCATORE: Sig.ra Maria Bianchi, nata a Milano il 12/03/1975, residente in Via Roma 45, Milano (MI), C.F. BNCMRA75C52F205X
5
+
6
+ CONDUTTORE: Sig. Giuseppe Verdi, nato a Torino il 08/07/1990, residente in Via Garibaldi 23, Torino (TO), C.F. VRDGPP90L08L219Y
7
+
8
+ IMMOBILE: Appartamento sito in Milano, Via Manzoni 78, piano 3°, composto da 3 vani più servizi, superficie 85 mq, categoria catastale A/2, foglio 123, particella 456.
9
+
10
+ CONDIZIONI:
11
+ - Durata: 4 anni dal 01/09/2025 al 31/08/2029
12
+ - Canone mensile: € 1.200,00 (milleduecento/00)
13
+ - Deposito cauzionale: € 2.400,00 (due trimestri)
14
+ - Spese condominiali: a carico del conduttore, stimate in € 150,00 mensili
15
+
16
+ CLAUSOLE PARTICOLARI:
17
+ 1. Il conduttore si impegna a mantenere l'immobile in buono stato di conservazione
18
+ 2. Non sono ammessi animali domestici
19
+ 3. Il pagamento deve avvenire entro il giorno 5 di ogni mese
20
+ 4. Eventuali modifiche strutturali devono essere preventivamente autorizzate
21
+ 5. Il conduttore è responsabile delle utenze (luce, gas, acqua, telefono)
22
+
23
+ Il presente contratto è regolato dalla Legge 431/98 e successive modificazioni.
24
+
25
+ Milano, 25 agosto 2025
26
+
27
+ Firma Locatore: _________________ Firma Conduttore: _________________
28
+
29
+ Registrato a Milano il _________ al n. _______ Serie ___
data/email1.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Gentile Dott. Rossi,
2
+
3
+ mi chiamo Luca Bianchi, nato a Milano il 12/03/1985, attualmente residente in Via Roma 24, 20121 Milano.
4
+
5
+ Le scrivo in merito a un accredito errato sul mio conto bancario IT60X0542811101000000123456 presso la banca Intesa Sanpaolo.
6
+ In allegato trova una copia del mio codice fiscale: BNCGLC85C12F205X.
7
+
8
+ In attesa di un suo riscontro, porgo cordiali saluti.
9
+ Luca Bianchi
10
+ luca.bianchi85@gmail.com
11
+ Telefono: +39 349 123 4567
data/email2.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Oggetto: INACCETTABILE - Ritardo nella consegna e prodotto danneggiato
2
+
3
+ Gentili Signori,
4
+
5
+ sono ESTREMAMENTE deluso e arrabbiato per il servizio pessimo che ho ricevuto dalla vostra azienda.
6
+
7
+ Ho ordinato il 15 maggio un laptop del valore di €1.200 con consegna garantita entro 5 giorni lavorativi. Sono passate DUE SETTIMANE e il prodotto è arrivato oggi in condizioni VERGOGNOSE: la scatola era completamente ammaccata e lo schermo del laptop presenta una crepa evidente.
8
+
9
+ Questo è inaccettabile! Ho pagato per un prodotto nuovo e perfetto, non per un rottame. Inoltre, la vostra assistenza clienti è stata inutile: nessuno ha risposto alle mie 4 chiamate e alle 3 email inviate.
10
+
11
+ Pretendo IMMEDIATAMENTE:
12
+ 1. La sostituzione completa del prodotto
13
+ 2. Il rimborso delle spese di spedizione
14
+ 3. Un risarcimento per il disagio causato
15
+
16
+ Se non riceverò una risposta entro 48 ore, sarò costretto a rivolgermi alle autorità competenti e a pubblicare recensioni negative su tutti i portali online.
17
+
18
+ Questa è l'ultima volta che ordino da voi.
19
+
20
+ Distinti saluti,
21
+ Marco Rossi
22
+ Tel: 339-1234567
23
+ Email: m.rossi@email.com
data/email3.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Oggetto: Grazie di cuore per il servizio eccezionale! ⭐⭐⭐⭐⭐
2
+
3
+ Carissimi,
4
+
5
+ non riesco a contenere la mia gioia e devo assolutamente condividere con voi quanto sono soddisfatto del vostro servizio!
6
+
7
+ Ho ricevuto ieri il set di mobili per il soggiorno che avevo ordinato due settimane fa, e devo dire che avete superato ogni mia aspettativa. La qualità è semplicemente fantastica, i materiali sono pregiati e la lavorazione è impeccabile. Inoltre, il team di montaggio è stato professionale, gentile e ha completato tutto in tempi record.
8
+
9
+ Ma ciò che mi ha colpito di più è stata l'attenzione ai dettagli: avete persino pulito tutto dopo il montaggio e mi avete spiegato come prendermi cura al meglio dei mobili. Questo è quello che chiamo VERO servizio clienti!
10
+
11
+ Sono così entusiasta che ho già consigliato la vostra azienda a tre amici, e sto già pensando di ordinare i mobili per la camera da letto.
12
+
13
+ Continuate così, siete davvero i migliori del settore!
14
+
15
+ Con immensa gratitudine,
16
+ Sofia Martinelli
17
+ 📧 s.martinelli@email.com
18
+ 📱 347-9876543
19
+
20
+ P.S. Allego alcune foto del risultato finale - sono orgogliosissima del mio nuovo soggiorno!
data/email4.txt ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Oggetto: Aggiornamento procedure sicurezza ufficio - Effettivo dal 01/07/2025
2
+
3
+ Gentili Colleghi,
4
+
5
+ con la presente comunico l'aggiornamento delle procedure di sicurezza che entreranno in vigore dal 1° luglio 2025, come da delibera del Consiglio di Amministrazione del 20 giugno 2025.
6
+
7
+ Le principali modifiche riguardano:
8
+
9
+ 1. ACCESSO AGLI UFFICI
10
+ - Obbligo di badge magnetico per tutti i dipendenti
11
+ - Registrazione orari di entrata e uscita tramite lettore automatico
12
+ - Accesso visitatori solo previo appuntamento e accompagnamento
13
+
14
+ 2. PROCEDURE EMERGENZA
15
+ - Nomina di due nuovi addetti antincendio per il piano terra
16
+ - Aggiornamento planimetrie di evacuazione (affisse in bacheca)
17
+ - Simulazione evacuazione programmata per il 15 luglio ore 11:00
18
+
19
+ 3. GESTIONE DOCUMENTI
20
+ - Archivio cartaceo: accesso solo con autorizzazione scritta del responsabile
21
+ - Documenti riservati: obbligo di utilizzo cassaforte ufficio
22
+ - Divieto di lasciare documenti sui piani di lavoro oltre l'orario di ufficio
23
+
24
+ Si ricorda che il mancato rispetto delle procedure comporterà l'applicazione del regolamento disciplinare aziendale.
25
+
26
+ Per chiarimenti contattare l'Ufficio Risorse Umane (int. 245).
27
+
28
+ Cordiali saluti,
29
+
30
+ Dott.ssa Elena Marchetti
31
+ Responsabile Risorse Umane
32
+ ALFA CONSULTING S.R.L.
33
+ elena.marchetti@alfaconsulting.it
34
+ Tel. 02-12345678 int. 201
data/fattura1.txt ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 🌟 FATTURA SERVIZI MATRIMONIALI 🌟
2
+ N° WED2025/078 - Data: 27/06/2025
3
+
4
+ DREAM WEDDING di Valentina Rossi & Co.
5
+ Via dei Fiori 23 - 20125 Milano (MI)
6
+ P.IVA: 11223344556 ✨
7
+ Tel: 02-5555777 | Email: info@dreamwedding.it
8
+ www.dreamwedding.it
9
+
10
+ CLIENTI:
11
+ Sig. Alessandro Conti & Sig.ra Giulia Romano
12
+ Via Primavera 12 - 20100 Milano (MI)
13
+ C.F. Alessandro: CNTLSN85M15F205K
14
+ C.F. Giulia: RMNGLA88D25F205P
15
+
16
+ 💕 MATRIMONIO DEL 15/06/2025 - VILLA GRAN PARADISO 💕
17
+
18
+ SERVIZI FORNITI:
19
+ 🎊 Wedding Planner completo (6 mesi coordinamento) € 3.500,00
20
+ 🌺 Allestimento floreale cerimonia e ricevimento € 2.200,00
21
+ 📸 Servizio fotografico professionale (10 ore) € 1.800,00
22
+ 🎵 DJ e service audio completo € 1.200,00
23
+ 🍰 Torta nuziale 3 piani (80 persone) € 450,00
24
+ 🎁 Bomboniere personalizzate (80 pezzi) € 320,00
25
+ ✨ Luci d'atmosfera e decorazioni extra € 680,00
26
+
27
+ TOTALE SERVIZI € 10.150,00
28
+ SCONTO FEDELTÀ CLIENTI AFFEZIONATI 10% 💝 € -1.015,00
29
+
30
+ IMPONIBILE € 9.135,00
31
+ IVA 22% € 2.009,70
32
+ TOTALE FATTURA € 11.144,70
33
+
34
+ SALDO (già versato acconto € 5.000,00) € 6.144,70
35
+
36
+ Pagamento: Bonifico bancico entro 30 giorni
37
+ IBAN: IT89 K033 5901 6001 0000 0012 345
38
+
39
+ Gentili Alessandro e Giulia,
40
+ è stato un onore organizzare il vostro matrimonio da sogno!
41
+ Grazie per averci scelto per il vostro giorno più bello! 💕
42
+
43
+ Con affetto,
44
+ Valentina e tutto il team Dream Wedding 🌟
data/fattura2.txt ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FATTURA N° 2025/0145
2
+ Data: 27/06/2025
3
+
4
+ STUDIO TECNICO ARCHITETTO LUIGI FERRETTI
5
+ Via Dante 67 - 20121 Milano (MI)
6
+ P.IVA: 12345678901
7
+ Tel: 02-1234567 - Email: info@studioferretti.it
8
+
9
+ CLIENTE:
10
+ IMMOBILIARE CENTRALE S.R.L.
11
+ Via Torino 45 - 20123 Milano (MI)
12
+ P.IVA: 98765432109
13
+ Codice Destinatario: ABC1234
14
+
15
+ DESCRIZIONE PRESTAZIONI:
16
+ - Progettazione architettonica preliminare uffici € 2.500,00
17
+ - Consulenza tecnica per pratiche edilizie € 800,00
18
+ - Sopralluogo e rilievo stato di fatto € 450,00
19
+ - Elaborazione planimetrie e sezioni € 600,00
20
+
21
+ TOTALE IMPONIBILE € 4.350,00
22
+ IVA 22% € 957,00
23
+ TOTALE FATTURA € 5.307,00
24
+
25
+ Contributo previdenziale 4% (Cassa Ingegneri e Architetti) € 174,00
26
+ Ritenuta d'acconto 20% (su imponibile + contributo) € 904,80
27
+
28
+ NETTO A PAGARE € 4.576,20
29
+
30
+ Modalità di pagamento: Bonifico bancario
31
+ Scadenza: 27/07/2025 (30 giorni data fattura)
32
+ IBAN: IT60 X054 2811 1010 0000 0123 456
33
+
34
+ Causale: Fattura n. 2025/0145 del 27/06/2025
data/hr_payslip.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Gentile Team HR di EY,
2
+
3
+ sono Giulia Verdi (giulia.verdi@outlook.it), assunta il 01/09/2022, e attualmente in servizio presso l’ufficio di Bologna.
4
+ Il mio codice fiscale è VRDGLL94A41A944C e il conto bancario per l'accredito dello stipendio è IT80P0306909606100000123456.
5
+
6
+ Vorrei sapere se è disponibile la Certificazione Unica (CU) 2024 e se verrà caricata sulla piattaforma MyEY.
7
+
8
+ Vi ringrazio anticipatamente.
9
+
10
+ Cordiali saluti,
11
+ Giulia Verdi
data/legal_communication.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Gentile Studio Legale Ferri,
2
+
3
+ mi chiamo Marco Neri, nato a Cagliari il 22/07/1979, attualmente residente in Via Dante 12, 09100 Cagliari.
4
+ La contatto in merito alla questione legale che coinvolge TechNova S.p.A., con sede a Roma, presso la quale ho lavorato dal 2018 al 2023.
5
+
6
+ I miei dati personali includono il codice fiscale NRIMRC79L22B354U e l’IBAN IT35Z0200801035000101123456.
7
+ Vi prego di confermare la ricezione del presente messaggio e di informarmi sui prossimi passi.
8
+
9
+ Cordiali saluti,
10
+ Marco Neri
11
+ marco.neri1979@hotmail.com
data/notifica.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Meeting con Giovanni alla Sala numero 2 il giorno 12 Marzo
data/report1.txt ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 🌟 RELAZIONE FINALE PROGETTO "SMART OFFICE 2025" 🌟
2
+ Data: 27 giugno 2025
3
+
4
+ Gentile Direzione Generale,
5
+
6
+ è con immensa soddisfazione e orgoglio che presento la relazione conclusiva del progetto "Smart Office 2025", che si è rivelato un SUCCESSO STRAORDINARIO oltre ogni più rosea aspettativa!
7
+
8
+ 📊 RISULTATI CONSEGUITI (Obiettivi superati del 150%!):
9
+
10
+ ✅ EFFICIENZA OPERATIVA
11
+ - Produttività aumentata del 45% (target: 25%)
12
+ - Tempi di elaborazione documenti: -60%
13
+ - Riduzione errori amministrativi: 89%
14
+ - Automazione processi: 95% procedure digitalizzate
15
+
16
+ ✅ SODDISFAZIONE DIPENDENTI
17
+ - Survey interna: 96% giudizi ECCELLENTI
18
+ - Richieste smart working: +300% (ma gestite perfettamente!)
19
+ - Assenteismo: ridotto del 35%
20
+ - Team building spontaneo: fenomeno bellissimo da osservare!
21
+
22
+ ✅ SOSTENIBILITÀ AMBIENTALE
23
+ - Consumo carta: -78% (abbiamo salvato una foresta! 🌳)
24
+ - Energia elettrica: -32% grazie ai sensori intelligenti
25
+ - Rifiuti ufficio: -65%
26
+ - Certificazione ISO 14001: OTTENUTA con punteggio massimo!
27
+
28
+ ✅ RISULTATI ECONOMICI FANTASTICI
29
+ - ROI: 340% in soli 6 mesi (incredibile!)
30
+ - Costi operativi: ridotti di € 180.000/anno
31
+ - Investimento recuperato: già al 85%
32
+ - Nuovi clienti acquisiti grazie all'immagine innovativa: +28%
33
+
34
+ 🎯 INNOVAZIONI DI MAGGIOR SUCCESSO:
35
+
36
+ 🤖 SISTEMA AI "SOFIA" (Smart Office Intelligent Assistant)
37
+ Il nostro assistente virtuale è diventato la mascotte dell'ufficio! Gestisce il 90% delle richieste routine e i colleghi hanno iniziato a chiamarla affettuosamente "la nostra Sofia". Ha persino imparato a ricordare i compleanni di tutti!
38
+
39
+ 📱 APP MOBILE "MyOffice"
40
+ Utilizzo quotidiano: 98% dipendenti. Funzionalità più amata: "Coffee Alert" che avvisa quando il caffè è appena pronto! Geniale e molto apprezzata.
41
+
42
+ 🌐 PIATTAFORMA COLLABORATIVA CLOUD
43
+ Zero downtime in 6 mesi! Collaborazione remota fluida al 100%. Alcuni clienti ci hanno chiesto di replicare il sistema per loro!
44
+
45
+ 💚 WELLNESS CORNER DIGITALE
46
+ Monitoraggio qualità aria, suggerimenti pausa, playlist personalizzate. Il wellness score medio dell'ufficio è "OTTIMO" per il 94% del tempo lavorativo.
47
+
48
+ 🏆 RICONOSCIMENTI ESTERNI:
49
+ - Premio "Innovazione Digitale 2025" - Camera di Commercio Milano
50
+ - Caso studio presentato al convegno "Future of Work" - Bocconi
51
+ - Articolo su Harvard Business Review Italia (in uscita!)
52
+ - Richieste visite studio da 12 aziende europee
53
+
54
+ 👏 IL TEAM DEL PROGETTO:
55
+ Voglio ringraziare pubblicamente il team eccezionale che ha reso possibile questo miracolo:
56
+ - Ing. Laura Colombo (Project Manager): Leadership ispirazionale
57
+ - Dr. Andrea Fontana (IT Specialist): Genio tecnico
58
+ - Dott.ssa Marta Pellegrini (Change Manager): Empatia e professionalità
59
+ - Tutto il team di sviluppo: 15 persone STRAORDINARIE
60
+
61
+ 🎉 CONCLUSIONI:
62
+ Il progetto "Smart Office 2025" non è solo riuscito, è diventato un MODELLO per l'intero settore! Abbiamo trasformato il nostro ambiente di lavoro in un ecosistema digitale armonioso dove tecnologia e umanità convivono perfettamente.
63
+
64
+ I dipendenti arrivano al mattino con il sorriso, i clienti sono entusiasti delle nostre innovazioni, e la competitività aziendale è aumentata esponenzialmente.
65
+
66
+ 🚀 PROSSIMI PASSI:
67
+ Propongo di avviare immediatamente "Smart Office 3.0" per consolidare la nostra leadership tecnologica!
68
+
69
+ Con gratitudine e entusiasmo per i successi futuri,
70
+
71
+ Dott.ssa Chiara Benedetti
72
+ Direttore Innovazione e Sviluppo
73
+ c.benedetti@azienda.it | Tel: 02-7777999
74
+
75
+ P.S. Allego 47 foto del progetto e 200+ feedback entusiastici dei dipendenti! 📸✨
data/report2.txt ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ REPORT URGENTE - GRAVI DISFUNZIONI SISTEMA INFORMATICO
2
+ Data: 27/06/2025 - Priorità: MASSIMA
3
+
4
+ Destinatario: Direzione Generale, CTO, Responsabile IT
5
+ Da: Dott. Francesco Lombardi - Responsabile Operazioni
6
+
7
+ PREMESSA:
8
+ La situazione del sistema informatico aziendale è DEGENERATA oltre ogni limite di tolleranza. I continui malfunzionamenti stanno paralizzando l'intera operatività aziendale con danni economici incalcolabili.
9
+
10
+ PROBLEMI CRITICI RILEVATI:
11
+
12
+ 1. SERVER PRINCIPALE - COLLASSI QUOTIDIANI
13
+ - 15 blocchi totali negli ultimi 7 giorni
14
+ - Perdita dati: 3 database corrotti irreversibilmente
15
+ - Downtime complessivo: 47 ore in una settimana
16
+ - INACCETTABILE per un'azienda della nostra dimensione!
17
+
18
+ 2. RETE AZIENDALE - PRESTAZIONI DISASTROSE
19
+ - Velocità di connessione: 20% rispetto ai parametri contrattuali
20
+ - Email server: funzionante solo 6 ore su 24
21
+ - Condivisione files: IMPOSSIBILE da 5 giorni
22
+ - I dipendenti NON RIESCONO A LAVORARE!
23
+
24
+ 3. SOFTWARE GESTIONALE - COMPLETAMENTE INADEGUATO
25
+ - Crash dell'applicazione: 8-10 volte al giorno
26
+ - Dati clienti inaccessibili da 72 ore
27
+ - Fatturazione elettronica: BLOCCATA da giovedì
28
+ - Contabilità: calcoli errati per € 50.000
29
+
30
+ 4. BACKUP SYSTEM - TOTALMENTE COMPROMESSO
31
+ - Ultimo backup funzionante: 12 giorni fa
32
+ - Sistema di sicurezza: DISATTIVATO da settimane
33
+ - Rischio perdita TUTTI i dati aziendali
34
+
35
+ CONSEGUENZE DEVASTANTI:
36
+ - Ordini clienti persi: € 120.000 in 3 giorni
37
+ - Reclami clienti: +400% rispetto al mese scorso
38
+ - Produttività dipendenti: -70%
39
+ - Reputation aziendale: COMPROMESSA GRAVEMENTE
40
+
41
+ AZIONI IMMEDIATE RICHIESTE:
42
+ 1. Sostituzione TOTALE dell'infrastruttura IT entro 48 ore
43
+ 2. Contratto assistenza H24 con azienda specializzata SERIA
44
+ 3. Licenziamento immediato del fornitore attuale
45
+ 4. Budget emergenza: minimo € 200.000
46
+
47
+ Se non si interviene SUBITO, l'azienda rischia il fallimento!
48
+
49
+ La mia responsabilità è avvisarvi: HO FATTO IL MIO DOVERE.
50
+ Ora spetta alla Direzione AGIRE IMMEDIATAMENTE!
51
+
52
+ Dott. Francesco Lombardi
53
+ Responsabile Operazioni
54
+ f.lombardi@azienda.it - Tel. 02-9999888 (SEMPRE RAGGIUNGIBILE)
55
+
56
+ NOTA: Invio copia per conoscenza anche al Consiglio di Amministrazione
docs/ARCHITECTURE.md ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agentic RAG Advanced Documentation
2
+
3
+ ![Project Status: In Progress](https://img.shields.io/badge/status-in%20progress-orange)
4
+ ![Python Version](https://img.shields.io/badge/python-3.9%2B-blue)
5
+ ![LangChain](https://img.shields.io/badge/LangChain-Enabled-brightgreen)
6
+ ![Azure OpenAI](https://img.shields.io/badge/Azure%20OpenAI-GPT--4-informational)
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+ 1. [Advanced Architecture Overview](#advanced-architecture-overview)
12
+ - [System Architecture Diagram](#system-architecture-diagram)
13
+ - [Process Flow Diagrams](#process-flow-diagrams)
14
+ - [Component Interactions](#component-interactions)
15
+ - [Database Schema](#database-schema)
16
+ 2. [Specialized Guides](#specialized-guides)
17
+ - [DevOps Deployment Guide](#devops-deployment-guide)
18
+ - [Security Best Practices](#security-best-practices)
19
+ - [Performance Tuning](#performance-tuning)
20
+ - [Monitoring and Observability](#monitoring-and-observability)
21
+ - [Testing Strategies](#testing-strategies)
22
+ 3. [In-Depth Technical Documentation](#in-depth-technical-documentation)
23
+ - [Design Patterns](#design-patterns)
24
+ - [Architectural Decisions and Trade-Offs](#architectural-decisions-and-trade-offs)
25
+ - [Dependency Mapping](#dependency-mapping)
26
+ - [Error Handling Strategy](#error-handling-strategy)
27
+ 4. [Templates and Boilerplate](#templates-and-boilerplate)
28
+ - [Feature Template](#feature-template)
29
+ - [Example Configurations](#example-configurations)
30
+ - [Automation Scripts](#automation-scripts)
31
+ - [Docker and Kubernetes Configurations](#docker-and-kubernetes-configurations)
32
+ 5. [Governance and Processes](#governance-and-processes)
33
+ - [Code Review Guidelines](#code-review-guidelines)
34
+ - [Branch Strategy](#branch-strategy)
35
+ - [CI/CD Pipeline](#ci/cd-pipeline)
36
+ - [Release Process](#release-process)
37
+
38
+ ---
39
+
40
+ ## Advanced Architecture Overview
41
+
42
+ ### System Architecture Diagram
43
+
44
+ ```mermaid
45
+ graph TD
46
+ User[User Interface (Streamlit)] -->|Sends Requests| API[REST API Gateway]
47
+ API -->|Processes Requests| Orchestrator[Multi-Agent Orchestrator (CrewAI)]
48
+ Orchestrator -->|Anonymize Data| NER[NER Anonymizer Module]
49
+ Orchestrator -->|Query GPT-4| GPT[Azure GPT-4 Processor]
50
+ Orchestrator -->|Retrieve Data| DB[Document Vector Database (FAISS)]
51
+ GPT --> Dashboard[Interactive Dashboard]
52
+ DB --> GPT
53
+ NER --> Dashboard
54
+ ```
55
+
56
+ ---
57
+
58
+ ### Process Flow Diagrams
59
+
60
+ #### Data Anonymization Flow
61
+
62
+ ```mermaid
63
+ flowchart TD
64
+ Start[Input Document]
65
+ Start --> |Recognize Entities| NER[NER Anonymization Engine]
66
+ NER --> |Mask Sensitive Information| MaskedDoc[Masked Document]
67
+ MaskedDoc --> |Save to Secure DB| DB[(Database)]
68
+ MaskedDoc --> Output[Anonymized Output]
69
+ ```
70
+
71
+ #### Semantic Query Workflow
72
+
73
+ ```mermaid
74
+ flowchart TD
75
+ Query[User Query] -->|Embed Query| Embedding[Embedding Generation]
76
+ Embedding -->|Search Similar Vectors| FAISS[FAISS Database]
77
+ FAISS -->|Retrieve Relevant Context| Context[Contextual Data]
78
+ Context -->|Augment Query| GPT[Azure GPT-4]
79
+ GPT --> Answer[Generated Answer]
80
+ ```
81
+
82
+ ---
83
+
84
+ ### Component Interactions
85
+
86
+ ```mermaid
87
+ graph LR
88
+ Frontend[User Interface] --> Backend[API Gateway]
89
+ Backend --> Orchestrator
90
+ Orchestrator --> Modules{Processing Modules}
91
+ Modules --> DB[Database]
92
+ Modules --> LLM[Azure GPT-4]
93
+ ```
94
+
95
+ ---
96
+
97
+ ### Database Schema
98
+
99
+ | **Table Name** | **Description** | **Key Fields** |
100
+ |----------------------|---------------------------------------|----------------------------|
101
+ | `documents` | Stores uploaded and processed docs | `doc_id`, `content` |
102
+ | `anonymous_entities` | Tracks anonymized entities | `entity_id`, `doc_id` |
103
+ | `query_logs` | Logs semantic queries and responses | `query_id`, `timestamp` |
104
+
105
+ ---
106
+
107
+ ## Specialized Guides
108
+
109
+ ### DevOps Deployment Guide
110
+
111
+ 1. **Infrastructure Setup**:
112
+ - Provision an Azure Virtual Machine with at least **8 CPUs and 32GB RAM**.
113
+ - Add storage for large-scale document processing.
114
+
115
+ 2. **Install Dependencies**:
116
+ ```bash
117
+ apt update && apt install -y python3.9 python3-pip docker.io
118
+ pip install -r requirements.txt
119
+ ```
120
+
121
+ 3. **Setup Docker**:
122
+ Create a `Dockerfile` for local builds:
123
+ ```dockerfile
124
+ FROM python:3.9-slim
125
+ WORKDIR /app
126
+ COPY . .
127
+ RUN pip install -r requirements.txt
128
+ CMD ["python", "main.py"]
129
+ ```
130
+
131
+ 4. **Deploy Using Docker Compose**:
132
+ ```yaml
133
+ version: '3'
134
+ services:
135
+ api:
136
+ build: .
137
+ ports:
138
+ - "8000:8000"
139
+ environment:
140
+ AZURE_KEY: "your-azure-key"
141
+ ```
142
+
143
+ 5. **Kubernetes Deployment**: Refer to [this guide](#docker-and-kubernetes-configurations).
144
+
145
+ ---
146
+
147
+ ### Security Best Practices
148
+
149
+ - **API Keys Management**: Use Azure Managed Service Identity for secure secrets storage.
150
+ - **Data Encryption**: Ensure TLS/SSL encryption for all API traffic.
151
+ - **Access Control**: Implement Role-Based Access Control (RBAC) for sensitive endpoints.
152
+
153
+ ---
154
+
155
+ ### Performance Tuning
156
+
157
+ 1. Optimize GPT-4 querying by using embeddings for context filtering prior to API requests.
158
+ 2. Enable multi-threading in the CrewAI orchestrator to handle concurrent tasks.
159
+
160
+ ---
161
+
162
+ ### Monitoring and Observability
163
+
164
+ - **Prometheus Integration**: Export metrics for anonymization time, query processing, and API latency.
165
+ - **Grafana Dashboards**: Visualize real-time pipeline performance.
166
+
167
+ ---
168
+
169
+ ### Testing Strategies
170
+
171
+ 1. **Unit Testing**: For individual modules (`pytest` recommended).
172
+ 2. **Integration Testing**: Simulate end-to-end document anonymization and querying.
173
+ 3. **Load Testing**: Verify performance at scale using `locust.io`.
174
+
175
+ ---
176
+
177
+ ## In-Depth Technical Documentation
178
+
179
+ ### Design Patterns
180
+
181
+ - **Pipeline Pattern**: For sequential document processing.
182
+ - **Microservices**: Each module (NER, RAG, etc.) is stateless and deployable as an independent service.
183
+
184
+ ---
185
+
186
+ ### Architectural Decisions and Trade-Offs
187
+
188
+ - **Database Choice**: Chose FAISS for fast vector processing over traditional SQL solutions.
189
+ - **Cloud Provider**: Azure selected for GPT-4 and machine-learning optimizations.
190
+
191
+ ---
192
+
193
+ ### Dependency Mapping
194
+
195
+ | **Dependency** | **Version** | **Purpose** |
196
+ |-------------------|----------------|--------------------------------------------|
197
+ | `LangChain` | `>=0.5.0` | RAG implementation |
198
+ | `transformers` | `>=4.10.0` | NER and embeddings |
199
+ | `faiss-cpu` | `>=1.7.0` | Vector search database |
200
+
201
+ ---
202
+
203
+ ### Error Handling Strategy
204
+
205
+ - **Retries**: Use exponential backoff for Azure API calls.
206
+ - **Logging**: Ensure all errors are logged to a central ELK stack (Elasticsearch, Logstash, Kibana).
207
+
208
+ ---
209
+
210
+ ## Templates and Boilerplate
211
+
212
+ ### Feature Template
213
+
214
+ **Feature Name**:
215
+ Description:
216
+ Owner:
217
+
218
+ ---
219
+
220
+ ### Example Configurations
221
+
222
+ **Streamlit Configuration:**
223
+ ```python
224
+ [server]
225
+ headless = true
226
+ port = 8501
227
+ ```
228
+
229
+ ---
230
+
231
+ ### Automation Scripts
232
+
233
+ - **Deployment Automation**:
234
+ ```bash
235
+ ./deploy.sh
236
+ ```
237
+
238
+ ---
239
+
240
+ ### Docker and Kubernetes Configurations
241
+
242
+ ```yaml
243
+ apiVersion: apps/v1
244
+ kind: Deployment
245
+ metadata:
246
+ name: agentic-rag
247
+ spec:
248
+ replicas: 3
249
+ selector:
250
+ matchLabels:
251
+ app: agentic-rag
252
+ template:
253
+ metadata:
254
+ labels:
255
+ app: agentic-rag
256
+ spec:
257
+ containers:
258
+ - name: agentic-rag-api
259
+ image: agentic-rag:latest
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Governance and Processes
265
+
266
+ ### Code Review Guidelines
267
+
268
+ - Ensure all new features include unit tests.
269
+ - Verify adherence to the PEP-8 coding standard.
270
+
271
+ ---
272
+
273
+ ### Branch Strategy
274
+
275
+ - Use **GitFlow** with `feature/`, `release/`, and `hotfix/` prefixes.
276
+
277
+ ---
278
+
279
+ ### CI/CD Pipeline
280
+
281
+ 1. Automated builds on `push` events to `main`.
282
+ 2. Deploy to staging for all pull requests.
283
+
284
+ ---
285
+
286
+ ### Release Process
287
+
288
+ - Generate a changelog using conventional commits.
289
+ - Tag releases with semantic versioning (`vX.Y.Z`).
290
+
291
+ ---
docs/INDEX.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 📚 Indice Documentazione
2
+
3
+ Documentazione tecnica completa generata automaticamente per questo progetto.
4
+
5
+ ## 🎯 Panoramica
6
+
7
+ Questa documentazione è stata creata utilizzando un sistema di **3 agenti CrewAI specializzati** che hanno analizzato il progetto per fornire:
8
+
9
+ - **Analisi architetturale** completa
10
+ - **Guide tecniche** dettagliate
11
+ - **Template riutilizzabili** per future implementazioni
12
+ - **Best practices** e raccomandazioni
13
+
14
+ ---
15
+
16
+ ## 📋 Documenti Disponibili
17
+
18
+ ### 🚀 **Documenti Principali**
19
+
20
+ #### 📖 **Documentazione Principale**
21
+ **File**: [`README.md`](README.md)
22
+ **Descrizione**: Guida completa con installazione, configurazione e utilizzo del progetto
23
+
24
+ #### 🏗️ **Architettura e Design**
25
+ **File**: [`ARCHITECTURE.md`](ARCHITECTURE.md)
26
+ **Descrizione**: Diagrammi di sistema, design patterns, deployment e best practices
27
+
28
+ #### 📋 **Template Riutilizzabili**
29
+ **File**: [`PROMPT_TEMPLATE.md`](PROMPT_TEMPLATE.md)
30
+ **Descrizione**: Template e prompt per generare documentazione simile per altri progetti
31
+
32
+ #### 🔍 **Analisi Tecnica**
33
+ **File**: [`TECHNICAL_ANALYSIS.md`](TECHNICAL_ANALYSIS.md)
34
+ **Descrizione**: Analisi approfondita di tecnologie, dipendenze e decisioni implementative
35
+
36
+ ---
37
+
38
+ ## 🗂️ Struttura Documentazione
39
+
40
+ ```
41
+ docs/
42
+ ├── 📖 README.md # Guida principale e installazione
43
+ ├── 🏗️ ARCHITECTURE.md # Architettura e diagrammi
44
+ ├── 🔍 TECHNICAL_ANALYSIS.md # Analisi tecnica dettagliata
45
+ ├── 📋 PROMPT_TEMPLATE.md # Template riutilizzabili
46
+ └── 📑 INDEX.md # Questo indice
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 🚀 Percorso di Lettura Consigliato
52
+
53
+ ### 👋 **Per Nuovi Utenti**
54
+ 1. **Inizia qui**: [`README.md`](README.md) - Setup e primo utilizzo
55
+ 2. **Comprendi il progetto**: [`TECHNICAL_ANALYSIS.md`](TECHNICAL_ANALYSIS.md) - Panoramica tecnica
56
+
57
+ ### 🛠️ **Per Sviluppatori**
58
+ 1. **Architettura**: [`ARCHITECTURE.md`](ARCHITECTURE.md) - Design e best practices
59
+ 2. **Implementazione**: [`TECHNICAL_ANALYSIS.md`](TECHNICAL_ANALYSIS.md) - Dettagli tecnici
60
+ 3. **Estensioni**: [`PROMPT_TEMPLATE.md`](PROMPT_TEMPLATE.md) - Template per nuove feature
61
+
62
+ ### 🚀 **Per DevOps/Deploy**
63
+ 1. **Setup**: [`README.md`](README.md) - Installazione e configurazione
64
+ 2. **Architettura**: [`ARCHITECTURE.md`](ARCHITECTURE.md) - Deployment e monitoring
65
+
66
+ ---
67
+
68
+ ## 📊 Statistiche Documentazione
69
+
70
+ - **📁 File documentazione**: 4
71
+ - **🤖 Agenti utilizzati**: 3 (DocumentAnalyzer, ContentProcessor, Documentation)
72
+ - **📅 Generato il**: 2025-06-30 14:46:10
73
+ - **🔄 Aggiornamenti**: Rigenerabile in qualsiasi momento
74
+
75
+ ---
76
+
77
+ ## 🎯 Come Usare Questa Documentazione
78
+
79
+ ### 📖 **Lettura Sequenziale**
80
+ Segui l'ordine consigliato sopra per una comprensione completa del progetto.
81
+
82
+ ### 🔍 **Consultazione Specifica**
83
+ Vai direttamente al documento che ti interessa usando i link sopra.
84
+
85
+ ### 📋 **Riferimenti Futuri**
86
+ Usa i template per creare documentazione simile per altri progetti.
87
+
88
+ ---
89
+
90
+ ## 🔄 Aggiornamento Documentazione
91
+
92
+ Questa documentazione è **completamente rigenerabile**. Per aggiornare:
93
+
94
+ 1. **Ricarica il progetto** nel sistema CrewAI
95
+ 2. **Riesegui l'analisi** completa
96
+ 3. **Scarica la nuova versione** documentata
97
+
98
+ I template in [`PROMPT_TEMPLATE.md`](PROMPT_TEMPLATE.md) permettono di **mantenere coerenza** tra diverse versioni della documentazione.
99
+
100
+ ---
101
+
102
+ ## 📞 Supporto
103
+
104
+ - **🐛 Problemi**: Consulta prima [`README.md`](README.md) e [`TECHNICAL_ANALYSIS.md`](TECHNICAL_ANALYSIS.md)
105
+ - **🏗️ Architettura**: Vedi [`ARCHITECTURE.md`](ARCHITECTURE.md) per questioni di design
106
+ - **🔧 Personalizzazioni**: Usa [`PROMPT_TEMPLATE.md`](PROMPT_TEMPLATE.md) come base
107
+
108
+ ---
109
+
110
+ *🤖 Documentazione generata automaticamente dal sistema CrewAI con 3 agenti specializzati*
111
+
112
+ *✨ Per rigenerare questa documentazione o applicarla ad altri progetti, utilizza i template forniti*
docs/PROMPT_TEMPLATE.md ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prompt Template
2
+
3
+ Questo template è stato generato automaticamente per il progetto.
4
+
5
+ ## Template
6
+
7
+ ```
8
+ ---
9
+
10
+ ## Prompt Template: **Modulare e Riutilizzabile per Progetto Tecnico**
11
+
12
+ ### **SEZIONE CONTESTO**
13
+ ```
14
+ # **Descrizione del Progetto**
15
+ Descrivi brevemente il progetto, includendo obiettivi principali e scopo.
16
+
17
+ **Nome Progetto**: {PROJECT_NAME}
18
+ **Descrizione Generale**: {PROJECT_DESCRIPTION}
19
+ - **Obiettivi Principali**:
20
+ 1. {OBJECTIVE_1}
21
+ 2. {OBJECTIVE_2}
22
+ 3. {OBJECTIVE_3}
23
+
24
+ **Tecnologie Utilizzate**:
25
+ - Linguaggi: {LANGUAGES_USED}
26
+ - Librerie/Framework:
27
+ 1. {LIBRARY_1}
28
+ 2. {LIBRARY_2}
29
+ 3. {LIBRARY_3}
30
+
31
+ **Architettura del Progetto**:
32
+ - Struttura generale: {ARCHITECTURE_OVERVIEW}
33
+ - Moduli chiave:
34
+ - {MODULE_1}
35
+ - {MODULE_2}
36
+ - {MODULE_3}
37
+
38
+ **Obiettivi e Requisiti Funzionali**:
39
+ 1. {REQUIREMENT_1}
40
+ 2. {REQUIREMENT_2}
41
+ 3. {REQUIREMENT_3}
42
+ ```
43
+
44
+ ---
45
+
46
+ ### **SEZIONE ISTRUZIONI**
47
+ ```
48
+ # **Istruzioni per Implementazione o Modifica**
49
+ Fornisci istruzioni dettagliate con placeholder che possono essere adattati a task specifici.
50
+
51
+ ### **Passaggi da Seguire per Completare il Task**:
52
+ 1. **Input e Setup**:
53
+ - Carica il file o dataset di esempio in formato: {INPUT_FORMAT}
54
+ - Configura le variabili d’ambiente utilizzando il file `{CONFIG_FILE}`.
55
+
56
+ 2. **Anonimizzazione e Analisi**:
57
+ - Utilizza il modulo `{ANONYMIZATION_MODULE}` per eseguire l'anonimizzazione dei dati con il seguente comando:
58
+ ```
59
+ python {SCRIPT_NAME} --input {INPUT_PATH} --output {OUTPUT_PATH}
60
+ ```
61
+ - Per implementare un nuovo modello di NER, inserisci il modello `{NEW_MODEL_NAME}` nella configurazione del modulo `{NER_MODULE}`.
62
+
63
+ 3. **Integrazione Multi-Agente**:
64
+ - Definisci gli agenti richiesti nel file `{AGENT_CONFIG_FILE}`.
65
+ - Avvia la pipeline tramite il comando:
66
+ ```
67
+ python {AGENT_SCRIPT} --config {AGENT_CONFIG_PATH}
68
+ ```
69
+
70
+ 4. **Modifica o Implementazione Specifica**:
71
+ - Sostituisci `{PLACEHOLDER_CODE_OR_FUNCTION}` nel modulo `{SPECIFIC_MODULE}` come segue:
72
+ ```
73
+ def {FUNCTION_NAME}(params):
74
+ # New implementation here
75
+ return updated_result
76
+ ```
77
+
78
+ ### **Dettagli di Configurazione**
79
+ - File di configurazione richiesti:
80
+ - `{CONFIG_FILE_1}`
81
+ - `{CONFIG_FILE_2}`
82
+
83
+ - Variabili d’ambiente chiave:
84
+ ```
85
+ API_KEY={YOUR_API_KEY}
86
+ ENDPOINT={YOUR_ENDPOINT}
87
+ MODEL_NAME={MODEL_NAME}
88
+ ```
89
+
90
+ ### **Good Practices**
91
+ - **Backup**: Effettua un backup dei dati caricati nella cartella `{BACKUP_FOLDER}` prima di processarli.
92
+ - **Logging**: Utilizza sempre il modulo `{LOGGING_MODULE}` per monitorare l'esecuzione.
93
+ ```
94
+
95
+ ---
96
+
97
+ ### **SEZIONE ESEMPI**
98
+ ```
99
+ # **Codice di Esempio**
100
+
101
+ ### **Anonimizzazione con Modulistica NER**
102
+ Esegui un mascheramento di dati sensibili utilizzando una regex e modelli NER.
103
+ ```python
104
+ from transformers import pipeline
105
+ import re
106
+
107
+ def anonymize_text(text):
108
+ # Named Entity Recognition
109
+ ner_model = pipeline("ner", model="{MODEL_NAME}", tokenizer="{TOKENIZER_NAME}")
110
+ entities = ner_model(text)
111
+
112
+ # Mascherare con regex entità sensibili
113
+ anonymized_text = re.sub(r"{PATTERN}", "{MASKING_VALUE}", text)
114
+ return anonymized_text
115
+
116
+ input_text = "Informazioni sensibili: Nome=John, IBAN=DE89 3704 0044 0532 0130 00."
117
+ print(anonymize_text(input_text))
118
+ ```
119
+
120
+ ### **Esempio di RAG Workflow con LangChain**
121
+ Esegui il retrieval semantico su una knowledge base per rispondere a domande.
122
+ ```python
123
+ from langchain.chains import RetrievalQA
124
+ from langchain.vectorstores import FAISS
125
+ from langchain.llms.openai import OpenAI
126
+
127
+ # Setup del modello e vector store
128
+ vector_store = FAISS.load_local("{VECTOR_STORE_PATH}")
129
+ qa_chain = RetrievalQA(llm=OpenAI(model="{GPT_MODEL}"), retriever=vector_store.as_retriever())
130
+
131
+ # Domanda di esempio
132
+ query = "Qual è l'analisi contenuta nel documento X?"
133
+ response = qa_chain.run(query)
134
+ print(response)
135
+ ```
136
+
137
+ ### **Orchestrazione Multi-Agente**
138
+ Utilizza CrewAI per analisi distribuita.
139
+ ```python
140
+ from crewai.agent import Agent
141
+ from crewai.orchestrator import Orchestrator
142
+
143
+ # Definizione agenti
144
+ agent1 = Agent(name="SentimentAnalysisAgent", task="{TASK}", model="{MODEL_NAME}")
145
+ agent2 = Agent(name="SummarizationAgent", task="text_summary", model="{MODEL_NAME}")
146
+
147
+ # Orchestrazione
148
+ orchestrator = Orchestrator(agents=[agent1, agent2])
149
+ orchestrator.run(input_data="{INPUT_PATH}")
150
+ ```
151
+
152
+ ---
153
+
154
+ ### **SEZIONE OUTPUT**
155
+ ```
156
+ # **Formato Output Desiderato**
157
+ Specifica come l'output deve essere strutturato per soddisfare i criteri.
158
+
159
+ ### **Formato e Struttura dei Dati**
160
+ - Formato file: {OUTPUT_FORMAT}
161
+ - Struttura dei dati:
162
+ ```json
163
+ {
164
+ "document_id": "{ID}",
165
+ "analysis_results": {
166
+ "anonymization_status": "{STATUS}",
167
+ "key_insights": [
168
+ "{INSIGHT_1}",
169
+ "{INSIGHT_2}"
170
+ ]
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### **Criteri di Qualità dell’Output**
176
+ 1. **Accuratezza**: Dati anonimizzati al 100% con nessuna informazione sensibile visibile.
177
+ 2. **Completeness**: Ogni documento deve includere un set completo di analisi (anonimizzazione, sintesi, sentiment analysis).
178
+ 3. **Formato Consistente**: Risultati esportati come JSON, leggibile e standard.
179
+
180
+ ### **Guida per Validazione**
181
+ Esegui un controllo di validazione su campioni usando il modulo `{VALIDATION_MODULE}` e il comando:
182
+ ```
183
+ python validate.py --input {OUTPUT_PATH} --schema {SCHEMA_PATH}
184
+ ```
185
+ ```
186
+
187
+ ---
188
+
189
+ Questo prompt template modulare offre una struttura completa per descrivere, istruire e contestualizzare un progetto basato su tecnologie avanzate con Placeholders chiaramente definiti. È progettato per essere riutilizzabile su diversi tipi di implementazioni simili al progetto **Agentic RAG**.
190
+ ```
191
+
192
+ ## Come utilizzare
193
+
194
+ 1. Copia il template sopra
195
+ 2. Sostituisci le variabili con i valori appropriati
196
+ 3. Utilizza per generare documentazione simile
197
+
198
+ *Generato automaticamente il 2025-06-30 14:46:10*
docs/TECHNICAL_ANALYSIS.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Analisi Tecnica
2
+
3
+ ## Panoramica
4
+
5
+ ---
6
+ ## 1. **PANORAMICA GENERALE**
7
+
8
+ ### Tipo di Progetto/Contenuto
9
+ Il progetto denominato **Agentic RAG** si concentra sull'implementazione di una pipeline di elaborazione documentale automatizzata, con lo scopo di:
10
+ - Garantire **anonimizzazione completa dei dati sensibili** nei documenti tramite tecniche avanzate di riconoscimento entità (NER) e regex.
11
+ - Offrire **analisi semantica** e risposte intelligenti basandosi su modelli LLM (Large Language Models).
12
+ - Costruire una **piattaforma scalabile** che integri tecnologie multi-agente per processi di analisi avanzata.
13
+
14
+ ### Tecnologie Principali Identificate
15
+ - **Linguaggi**: Python.
16
+ - **Framework & librerie principali**:
17
+ - [LangChain](https://www.langchain.com): Per l'implementazione della Retrieval-Augmented Generation (RAG).
18
+ - [Streamlit](https://streamlit.io): Per la costruzione dell'interfaccia web.
19
+ - [Transformers](https://huggingface.co): Per tecniche di analisi NER con modelli come BERT.
20
+ - [Azure OpenAI](https://azure.microsoft.com/en-us/services/openai/): Per GPT-4 e gestioni embedding per la similarità semantica.
21
+ - CrewAI: Per orchestrazione di moduli multi-agente.
22
+ - **Soluzioni cloud**:
23
+ - Azure Integration per GPT-4, embeddings e API OpenAI.
24
+
25
+ ### Struttura Generale
26
+ - **Moduli Software**:
27
+ - `NERAnonimizer` per l'anonimizzazione.
28
+ - `AzureProcessor` per la gestione delle analisi tramite GPT-4.
29
+ - Multi-agente CrewAI per un'analisi distribuita.
30
+ - **Struttura di Presentazione**:
31
+ - Una dashboard interattiva basata su Streamlit.
32
+ - **Pipeline di Elaborazione**:
33
+ Il progetto segue un flusso operativo ben definito: **Upload → Anonimizzazione → Analisi → RAG → Multi-Agent Processing → Risultati Finali**.
34
+
35
+ ---
36
+
37
+ ## 2. **ANALISI TECNICA**
38
+
39
+ ### Linguaggi di Programmazione Utilizzati
40
+ - **Python**: Linguaggio principale identificato per tutti i livelli implementativi.
41
+
42
+ ### Framework e Librerie Identificate
43
+ - **LangChain**: Utilizzata per il retrieval semantico dei documenti e la costruzione di chatbot avanzati utilizzando il paradigma Retrieval-Augmented Generation.
44
+ - **Transformers**: Libreria Hugging Face integrata per implementare modelli di Named Entity Recognition (NER) come `"Davlan/bert-base-multilingual-cased-ner-hrl"`.
45
+ - **Streamlit**: Utilizzato per l'interfaccia grafica (dashboard web interattiva).
46
+ - **FAISS (Facebook AI Similarity Search)**: Per la creazione di un Index semantico di vector embedding.
47
+ - **OpenAI Integration**: Per connettività alla piattaforma Azure e utilizzo di modelli GPT-4 e specifici embedding.
48
+ - **dotenv**: Per la gestione delle variabili di configurazione (.env).
49
+
50
+ ### Pattern Architetturali Rilevati
51
+ - **Pipeline di Elaborazione Dati**:
52
+ 1. Analisi e anonimizzazione iniziale tramite moduli NER & regex.
53
+ 2. Creazione di una knowledge base con embeddings per query semantiche.
54
+ 3. Supporto multi-agente tramite CrewAI.
55
+ - **Architettura Layered** (a 5 livelli): Presentazione, Privacy, Semantica, Multi-Agent, Persistenza.
56
+ - **Design Orientato alla Privacy (Privacy by Design)**: Mascheramento dati prima di tutte le elaborazioni AI.
57
+
58
+ ### File di Configurazione Trovati
59
+ - `.env`: File caricati tramite `load_dotenv` per centralizzare:
60
+ - Chiavi e endpoint API di Azure.
61
+ - Configurazioni di deploy LLM (`gpt-4o`) e modelli NER.
62
+
63
+ ---
64
+
65
+ ## 3. **STRUTTURA ORGANIZZATIVA**
66
+
67
+ ### Organizzazione Cartelle/File
68
+ ```
69
+ Gruppo_2/
70
+ ├── 01_Agentic_RAG.py # Codice principale backend
71
+ ├── 01_risposta_progettuale.md # Analisi e documentazione high-level
72
+ ├── 02_Documentazione.md # Documentazione approfondita tecnica
73
+ ├── 02_schema_architetturale.md # Schema architetturale dettagliato
74
+ ├── 03_documenti/ # Documenti sample per test pipeline
75
+ ├── email3.txt
76
+ ├── email4.txt
77
+ ├── notifica.txt
78
+ ├── report2.txt
79
+ ├── 04_documenti/ # Folder per documenti generici anonimi
80
+ └── .env # Configurazione sensibile (.gitignored)
81
+ ```
82
+
83
+ ### Moduli Principali
84
+ 1. **`Config`**:
85
+ - Gestisce tutte le configurazioni centrali, incluse variabili di ambiente.
86
+ 2. **`NERAnonimizer`**:
87
+ - Effettua mascheramento dati sensibili tramite regex e BERT NER.
88
+ 3. **`AzureProcessor`**:
89
+ - Connette e utilizza GPT-4 e altre capacità AI offerte da Azure OpenAI.
90
+ 4. **`CrewAI`**:
91
+ - Orchestrazione di agenti per processi paralleli distribuiti (analisi multi-agente).
92
+
93
+ ### Punti di Ingresso (Entry Points)
94
+ - **`main()` in 01_Agentic_RAG.py**:
95
+ - Inizializza la pipeline principale.
96
+ - Caricamento documenti, setup agenti, esecuzione task CrewAI.
97
+ - **Streamlit Dashboard**:
98
+ - Entry point utente per operazioni gestionali.
99
+
100
+ ### Dipendenze Principali
101
+ - **Python Core Modules**:
102
+ - `os`, `re`, `json`, `tempfile`, `pathlib`, `pandas`, `numpy`.
103
+ - **Cloud Services**:
104
+ - Azure: API embedding, GPT-4 inclusa.
105
+ - **LLM e NLP Tools**:
106
+ - LangChain, Transformers, FAISS.
107
+ - **Strumenti interattivi**:
108
+ - Streamlit.
109
+
110
+ ---
111
+
112
+ ## 4. **CONTESTO FUNZIONALE**
113
+
114
+ ### Funzionalità Principali
115
+ 1. **Anonimizzazione Dati Sensibili**:
116
+ - Mascheramento di dati sensibili come IBAN, email, numeri di carte tramite regex.
117
+ - Riconoscimento di entità personali/organizzative attraverso NER multilingua.
118
+ 2. **Analisi e RAG Integration**:
119
+ - Recupero e analisi semantica con LangChain+FAISS.
120
+ - Codifica e costruzione di knowledge base nel vector store.
121
+ 3. **Processing Multi-Agente**:
122
+ - CrewAI consente orchestrazione di analisi parallelizzate suddivise in specializzazioni come sentiment analysis o sintesi documentale.
123
+ 4. **Reportistica ed Esportazione**:
124
+ - Generazione di file JSON contenenti tutte le analisi.
125
+ - Persistenza di cronologia, log, e risultati utente.
126
+
127
+ ### API o Interfacce Esposte
128
+ - **LangChain Vector Retrieval**:
129
+ - Accesso per query semantiche e augmented answers.
130
+ - **Streamlit GUI** (frontend integrato):
131
+ - Tab dedicati per: caricamento file, anonimizzazione, analisi, chatbot interattivi, e gestione multi-agente.
132
+
133
+ ### Processi di Business Identificati
134
+ - **Conformità Privacy (GDPR)**:
135
+ - Tutta l’elaborazione avviene su dati anonimizzati.
136
+ - Export finale contiene solo dati "sicuri".
137
+ - **Analisi Documentale Automatica**:
138
+ - I documenti caricati subiscono un flusso standardizzato di:
139
+ - Anonimizzazione.
140
+ - Classificazione.
141
+ - Sintesi semantica.
142
+ - Risposte intelligenti via RAG.
143
+
144
+ ### Workflow Principali
145
+ 1. **Data Processing Workflow**:
146
+ - Analisi completa basata sull'orchestrazione degli agenti CrewAI.
147
+ - Editing manuale opzionale sull'interfaccia Streamlit.
148
+ 2. **Collaborative Task Management**:
149
+ - Crew di agenti specializzati automatizza la gestione di più task su documenti multipli.
150
+
151
+ ---
152
+
153
+ **Complessivamente**, il progetto **Agentic RAG** implementa una pipeline avanzata focalizzata sull'anonimizzazione predittiva, analisi AI distribuita, e presentazione scalabile consolidando tecnologie moderne come NER, regex, LangChain e sistemi multi-agente per rispondere a bisogni legati a privacy, intelligenza aziendale e conformità normativa.
154
+
155
+ ## Metadata
156
+
157
+ - **Generato il**: 2025-06-30 14:46:10
158
+ - **Tipo sorgente**: File ZIP
159
+ - **Nome sorgente**: Gruppo_2.zip
index.html CHANGED
@@ -1,19 +1,19 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width" />
6
+ <title>My static Space</title>
7
+ <link rel="stylesheet" href="style.css" />
8
+ </head>
9
+ <body>
10
+ <div class="card">
11
+ <h1>Welcome to your static Space!</h1>
12
+ <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
+ <p>
14
+ Also don't forget to check the
15
+ <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
+ </p>
17
+ </div>
18
+ </body>
19
+ </html>
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ transformers
3
+ langchain
4
+ openai
5
+ python-dotenv
6
+ crewai
7
+ faiss-cpu
8
+ pandas
9
+ numpy
src/__init__.py ADDED
File without changes
src/__pycache__/ai_processor.cpython-313.pyc ADDED
Binary file (17.7 kB). View file
 
src/__pycache__/anonymizer.cpython-313.pyc ADDED
Binary file (5.09 kB). View file
 
src/__pycache__/config.cpython-313.pyc ADDED
Binary file (1.69 kB). View file
 
src/__pycache__/utils.cpython-313.pyc ADDED
Binary file (12.2 kB). View file
 
src/ai_processor.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tutti i componenti AI: Azure, RAG e CrewAI.
3
+ """
4
+
5
+ import re
6
+ from typing import Dict, List
7
+ import streamlit as st
8
+ from openai import AzureOpenAI
9
+
10
+ # LangChain imports
11
+ from langchain_text_splitters import CharacterTextSplitter
12
+ from langchain_openai import AzureOpenAIEmbeddings, AzureChatOpenAI
13
+ from langchain_community.vectorstores import FAISS
14
+ from langchain.chains import RetrievalQA
15
+ from langchain_core.prompts import PromptTemplate
16
+
17
+ # CrewAI imports
18
+ from crewai import Agent, Task, Crew
19
+ from crewai.llm import LLM
20
+
21
+ from config import Config
22
+
23
+ class AzureProcessor:
24
+ """Processore Azure OpenAI"""
25
+
26
+ def __init__(self):
27
+ self.client = None
28
+ self.setup_client()
29
+
30
+ def setup_client(self):
31
+ """Setup client Azure"""
32
+ if Config.AZURE_API_KEY and Config.AZURE_ENDPOINT:
33
+ try:
34
+ self.client = AzureOpenAI(
35
+ api_key=Config.AZURE_API_KEY,
36
+ api_version=Config.AZURE_API_VERSION,
37
+ azure_endpoint=Config.AZURE_ENDPOINT
38
+ )
39
+ except Exception as e:
40
+ st.error(f"Errore Azure OpenAI: {e}")
41
+ self.client = None
42
+ else:
43
+ st.warning("Credenziali Azure OpenAI non trovate.")
44
+
45
+ def process_document(self, anonymized_text: str) -> str:
46
+ """Processa documento con AI"""
47
+ if not self.client:
48
+ return "Azure OpenAI non configurato."
49
+
50
+ try:
51
+ messages = [
52
+ {
53
+ "role": "system",
54
+ "content": (
55
+ "Analizza il documento anonimizzato e fornisci:\n"
56
+ "1. Tipo di documento\n"
57
+ "2. Riepilogo (max 5 righe)\n"
58
+ "3. Analisi semantica (temi, sentiment)\n"
59
+ "4. Risposta suggerita se è comunicazione cliente\n"
60
+ "Usa solo i contenuti del documento fornito."
61
+ )
62
+ },
63
+ {
64
+ "role": "user",
65
+ "content": f"Analizza questo documento:\n\n{anonymized_text}"
66
+ }
67
+ ]
68
+
69
+ response = self.client.chat.completions.create(
70
+ model=Config.DEPLOYMENT_NAME,
71
+ messages=messages,
72
+ max_tokens=800,
73
+ temperature=0.7
74
+ )
75
+
76
+ return response.choices[0].message.content
77
+
78
+ except Exception as e:
79
+ return f"Errore analisi AI: {e}"
80
+
81
+ class RAGChatbot:
82
+ """Chatbot RAG con LangChain"""
83
+
84
+ def __init__(self):
85
+ self.vector_store = None
86
+ self.qa_chain = None
87
+ self.embeddings = None
88
+ self.llm = None
89
+ self.setup_langchain_components()
90
+
91
+ def setup_langchain_components(self):
92
+ """Setup componenti LangChain"""
93
+ if not (Config.AZURE_API_KEY and Config.AZURE_ENDPOINT and
94
+ Config.AZURE_EMBEDDING_API_KEY and Config.AZURE_EMBEDDING_ENDPOINT):
95
+ st.warning("Credenziali Azure incomplete. RAG non disponibile.")
96
+ return
97
+
98
+ try:
99
+ # Embeddings
100
+ self.embeddings = AzureOpenAIEmbeddings(
101
+ model=Config.AZURE_EMBEDDING_DEPLOYMENT_NAME,
102
+ api_version=Config.AZURE_API_VERSION,
103
+ azure_endpoint=Config.AZURE_EMBEDDING_ENDPOINT,
104
+ api_key=Config.AZURE_EMBEDDING_API_KEY,
105
+ chunk_size=16
106
+ )
107
+
108
+ # LLM
109
+ self.llm = AzureChatOpenAI(
110
+ deployment_name=Config.DEPLOYMENT_NAME,
111
+ azure_endpoint=Config.AZURE_ENDPOINT,
112
+ api_key=Config.AZURE_API_KEY,
113
+ api_version=Config.AZURE_API_VERSION,
114
+ temperature=0.2
115
+ )
116
+ except Exception as e:
117
+ st.error(f"Errore setup LangChain: {e}")
118
+ self.embeddings = None
119
+ self.llm = None
120
+
121
+ def build_vector_store(self, anonymized_docs: Dict[str, Dict]):
122
+ """Costruisce vector store FAISS"""
123
+ if not self.embeddings or not self.llm:
124
+ st.error("Componenti LangChain non configurati.")
125
+ return
126
+
127
+ # Prepara testi per RAG
128
+ all_texts = []
129
+ for filename, doc_data in anonymized_docs.items():
130
+ if doc_data.get('confirmed', False):
131
+ all_texts.append(f"Documento {filename}:\n{doc_data['anonymized']}")
132
+
133
+ if not all_texts:
134
+ st.warning("Nessun documento confermato per RAG.")
135
+ return
136
+
137
+ with st.spinner("Creando vector store..."):
138
+ # Chunking
139
+ combined_text = "\n\n".join(all_texts)
140
+ text_splitter = CharacterTextSplitter(
141
+ separator="\n\n",
142
+ chunk_size=1000,
143
+ chunk_overlap=200,
144
+ length_function=len,
145
+ )
146
+ texts = text_splitter.split_text(combined_text)
147
+
148
+ # Crea FAISS index
149
+ self.vector_store = FAISS.from_texts(texts, self.embeddings)
150
+ st.success(f"Vector store con {len(texts)} chunks creato.")
151
+
152
+ # Setup QA chain
153
+ qa_prompt = """Usa il contesto per rispondere alla domanda.
154
+ Se non sai la risposta, dillo chiaramente.
155
+
156
+ {context}
157
+
158
+ Domanda: {question}
159
+ Risposta:"""
160
+
161
+ QA_PROMPT = PromptTemplate.from_template(qa_prompt)
162
+
163
+ self.qa_chain = RetrievalQA.from_chain_type(
164
+ llm=self.llm,
165
+ chain_type="stuff",
166
+ retriever=self.vector_store.as_retriever(),
167
+ return_source_documents=True,
168
+ chain_type_kwargs={"prompt": QA_PROMPT}
169
+ )
170
+
171
+ def answer_question(self, query: str) -> str:
172
+ """Risponde usando RAG"""
173
+ if not self.qa_chain:
174
+ return "RAG non pronto. Costruisci prima il knowledge base."
175
+
176
+ try:
177
+ result = self.qa_chain.invoke({"query": query})
178
+ answer = result["result"]
179
+
180
+ # Aggiungi fonti se disponibili
181
+ source_docs = result.get("source_documents", [])
182
+ if source_docs:
183
+ answer += "\n\n**Fonti:**\n"
184
+ for i, doc in enumerate(source_docs):
185
+ match = re.search(r"Documento (.*?):\n", doc.page_content)
186
+ source_info = f" (da {match.group(1)})" if match else ""
187
+ answer += f"- ...{doc.page_content[-100:]}{source_info}\n"
188
+
189
+ return answer
190
+ except Exception as e:
191
+ return f"Errore RAG: {e}"
192
+
193
+ def get_relevant_context(self, query: str, max_docs: int = 3) -> str:
194
+ """Estrae contesto rilevante per query"""
195
+ if not self.vector_store:
196
+ return ""
197
+
198
+ try:
199
+ docs = self.vector_store.similarity_search(query, k=max_docs)
200
+ context = "\n\n".join([doc.page_content for doc in docs])
201
+ return context
202
+ except Exception as e:
203
+ return f"Errore contesto: {e}"
204
+
205
+ class CrewAIManager:
206
+ """Manager agenti CrewAI"""
207
+
208
+ def __init__(self, rag_chatbot: RAGChatbot):
209
+ self.rag_chatbot = rag_chatbot
210
+ self.agents = None
211
+ self.llm = None
212
+ self.setup_crew()
213
+
214
+ def setup_crew(self):
215
+ """Setup agenti CrewAI"""
216
+ if not Config.AZURE_API_KEY:
217
+ st.warning("Azure non disponibile per CrewAI")
218
+ return
219
+
220
+ try:
221
+ # LLM per CrewAI
222
+ self.llm = LLM(
223
+ model=f"azure/{Config.DEPLOYMENT_NAME}",
224
+ api_key=Config.AZURE_API_KEY,
225
+ base_url=Config.AZURE_ENDPOINT,
226
+ api_version=Config.AZURE_API_VERSION
227
+ )
228
+
229
+ # Agenti
230
+ document_analyst = Agent(
231
+ role="Document Analyst",
232
+ goal="Analizzare documenti anonimizzati e fornire insights",
233
+ backstory="Esperto analista documenti con focus su privacy e compliance. "
234
+ "Lavori solo con documenti anonimizzati per proteggere i dati.",
235
+ llm=self.llm,
236
+ verbose=True,
237
+ allow_delegation=False,
238
+ max_iter=3
239
+ )
240
+
241
+ rag_specialist = Agent(
242
+ role="RAG Specialist",
243
+ goal="Rispondere a domande usando il sistema RAG",
244
+ backstory="Esperto in Information Retrieval e RAG systems. "
245
+ "Specializzato nel recupero di informazioni da documenti anonimizzati.",
246
+ llm=self.llm,
247
+ verbose=True,
248
+ allow_delegation=False,
249
+ max_iter=3
250
+ )
251
+
252
+ sentiment_analyst = Agent(
253
+ role="Sentiment Analyst",
254
+ goal="Analizzare sentiment e emozioni nei documenti",
255
+ backstory="Esperto in sentiment analysis e behavioral analytics. "
256
+ "Identifichi emozioni, trend e segnali nei documenti.",
257
+ llm=self.llm,
258
+ verbose=True,
259
+ allow_delegation=False,
260
+ max_iter=3
261
+ )
262
+
263
+ strategy_coordinator = Agent(
264
+ role="Strategy Coordinator",
265
+ goal="Coordinare analisi e fornire raccomandazioni strategiche",
266
+ backstory="Senior consultant con background in strategic management. "
267
+ "Traduci insights tecnici in raccomandazioni business concrete.",
268
+ llm=self.llm,
269
+ verbose=True,
270
+ allow_delegation=True,
271
+ max_iter=4
272
+ )
273
+
274
+ self.agents = {
275
+ 'document_analyst': document_analyst,
276
+ 'rag_specialist': rag_specialist,
277
+ 'sentiment_analyst': sentiment_analyst,
278
+ 'strategy_coordinator': strategy_coordinator
279
+ }
280
+
281
+ st.success("✅ Agenti CrewAI configurati")
282
+
283
+ except Exception as e:
284
+ st.error(f"Errore setup CrewAI: {e}")
285
+ self.agents = None
286
+
287
+ def create_analysis_task(self, query: str, analysis_type: str = "comprehensive") -> str:
288
+ """Crea task di analisi per il crew"""
289
+ if not self.agents:
290
+ return "CrewAI non configurato"
291
+
292
+ try:
293
+ # Ottieni contesto dal RAG
294
+ context = self.rag_chatbot.get_relevant_context(query, max_docs=5)
295
+
296
+ tasks = []
297
+
298
+ if analysis_type in ["comprehensive", "document"]:
299
+ # Task analisi documentale
300
+ doc_task = Task(
301
+ description=f"""
302
+ Analizza documenti per: {query}
303
+
304
+ CONTESTO: {context}
305
+
306
+ Fornisci:
307
+ - Tipo e classificazione documenti
308
+ - Temi e argomenti principali
309
+ - Elementi rilevanti business
310
+ - Note compliance
311
+ """,
312
+ expected_output="Analisi strutturata con classificazione e insights",
313
+ agent=self.agents['document_analyst']
314
+ )
315
+ tasks.append(doc_task)
316
+
317
+ if analysis_type in ["comprehensive", "sentiment"]:
318
+ # Task sentiment
319
+ sentiment_task = Task(
320
+ description=f"""
321
+ Analizza sentiment per: {query}
322
+
323
+ CONTESTO: {context}
324
+
325
+ Valuta:
326
+ - Sentiment generale (scala 1-10)
327
+ - Emozioni prevalenti
328
+ - Trend comunicazioni
329
+ - Segnali rischio/opportunità
330
+ """,
331
+ expected_output="Analisi sentiment con valutazioni quantitative",
332
+ agent=self.agents['sentiment_analyst']
333
+ )
334
+ tasks.append(sentiment_task)
335
+
336
+ if analysis_type in ["comprehensive", "rag"]:
337
+ # Task RAG
338
+ rag_task = Task(
339
+ description=f"""
340
+ Rispondi usando RAG: {query}
341
+
342
+ CONTESTO: {context}
343
+
344
+ Includi:
345
+ - Risposta diretta
346
+ - Evidenze documenti
347
+ - Correlazioni trovate
348
+ - Informazioni mancanti
349
+ - Suggerimenti approfondimento
350
+ """,
351
+ expected_output="Risposta RAG con evidenze",
352
+ agent=self.agents['rag_specialist']
353
+ )
354
+ tasks.append(rag_task)
355
+
356
+ # Task coordinamento (sempre incluso)
357
+ coord_task = Task(
358
+ description=f"""
359
+ Sintetizza risultati per: {query}
360
+
361
+ Crea sintesi con:
362
+ - Executive Summary (3 punti)
363
+ - Insights strategici
364
+ - Raccomandazioni prioritarie
365
+ - Next steps concreti
366
+ - Valutazione rischi
367
+
368
+ Output executive-ready e actionable.
369
+ """,
370
+ expected_output="Sintesi strategica con raccomandazioni",
371
+ agent=self.agents['strategy_coordinator']
372
+ )
373
+ tasks.append(coord_task)
374
+
375
+ # Crea crew
376
+ crew = Crew(
377
+ agents=list(self.agents.values()),
378
+ tasks=tasks,
379
+ verbose=True
380
+ )
381
+
382
+ with st.spinner(f"Eseguendo analisi {analysis_type}..."):
383
+ result = crew.kickoff()
384
+
385
+ return str(result)
386
+
387
+ except Exception as e:
388
+ return f"Errore CrewAI: {e}"
389
+
390
+ def create_custom_task(self, query: str, selected_agents: List[str], custom_instructions: str = "") -> str:
391
+ """Task personalizzate con agenti specifici"""
392
+ if not self.agents:
393
+ return "CrewAI non configurato"
394
+
395
+ try:
396
+ context = self.rag_chatbot.get_relevant_context(query, max_docs=5)
397
+
398
+ tasks = []
399
+ agents_to_use = []
400
+
401
+ for agent_key in selected_agents:
402
+ if agent_key in self.agents:
403
+ agents_to_use.append(self.agents[agent_key])
404
+
405
+ task = Task(
406
+ description=f"""
407
+ {custom_instructions if custom_instructions else f'Analizza secondo il ruolo di {agent_key}'}
408
+
409
+ QUERY: {query}
410
+ CONTESTO: {context}
411
+
412
+ Fornisci analisi specializzata secondo il tuo ruolo.
413
+ """,
414
+ expected_output=f"Analisi specializzata da {agent_key}",
415
+ agent=self.agents[agent_key]
416
+ )
417
+ tasks.append(task)
418
+
419
+ if not tasks:
420
+ return "Nessun agente valido selezionato"
421
+
422
+ crew = Crew(
423
+ agents=agents_to_use,
424
+ tasks=tasks,
425
+ verbose=True
426
+ )
427
+
428
+ with st.spinner(f"Eseguendo task con {len(agents_to_use)} agenti..."):
429
+ result = crew.kickoff()
430
+
431
+ return str(result)
432
+
433
+ except Exception as e:
434
+ return f"Errore task personalizzato: {e}"
src/anonymizer.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Sistema di anonimizzazione con NER e regex.
3
+ """
4
+
5
+ import re
6
+ from typing import Dict, Tuple
7
+ from transformers import pipeline
8
+ import streamlit as st
9
+ from config import Config, REGEX_PATTERNS
10
+
11
+ class NERAnonimizer:
12
+ """Anonimizzatore con NER e regex"""
13
+
14
+ def __init__(self):
15
+ self.regex_patterns = REGEX_PATTERNS
16
+ self._ner_pipe = None
17
+
18
+ @property
19
+ def ner_pipe(self):
20
+ """Lazy loading del modello NER"""
21
+ if self._ner_pipe is None:
22
+ with st.spinner("Caricamento modello NER..."):
23
+ try:
24
+ self._ner_pipe = pipeline(
25
+ "ner",
26
+ model=Config.NER_MODEL,
27
+ aggregation_strategy="simple"
28
+ )
29
+ except Exception as e:
30
+ st.error(f"Errore caricamento NER: {e}")
31
+ return None
32
+ return self._ner_pipe
33
+
34
+ def mask_with_regex(self, text: str) -> Tuple[str, Dict]:
35
+ """Applica mascheramento con regex"""
36
+ masked_text = text
37
+ found_entities = {}
38
+
39
+ # Ordina pattern per lunghezza (più lunghi prima)
40
+ sorted_patterns = sorted(
41
+ self.regex_patterns.items(),
42
+ key=lambda item: len(item[1]),
43
+ reverse=True
44
+ )
45
+
46
+ for label, pattern in sorted_patterns:
47
+ matches = list(re.finditer(pattern, masked_text, flags=re.IGNORECASE))
48
+ for match in reversed(matches):
49
+ original = match.group()
50
+ if original.startswith('[') and original.endswith(']'):
51
+ continue
52
+
53
+ placeholder = f"[{label}_{len(found_entities)}]"
54
+ found_entities[placeholder] = original
55
+ masked_text = masked_text[:match.start()] + placeholder + masked_text[match.end():]
56
+
57
+ return masked_text, found_entities
58
+
59
+ def mask_with_ner(self, text: str) -> Tuple[str, Dict]:
60
+ """Applica mascheramento con NER"""
61
+ if not self.ner_pipe:
62
+ return text, {}
63
+
64
+ try:
65
+ entities = self.ner_pipe(text)
66
+ entity_map = {}
67
+
68
+ sorted_entities = sorted(entities, key=lambda x: x['start'], reverse=True)
69
+
70
+ for ent in sorted_entities:
71
+ if ent['score'] > 0.5:
72
+ label = ent['entity_group']
73
+ original_text = text[ent['start']:ent['end']]
74
+
75
+ if original_text.startswith('[') and original_text.endswith(']'):
76
+ continue
77
+
78
+ placeholder = f"[{label}_{len(entity_map)}]"
79
+ entity_map[placeholder] = original_text
80
+
81
+ text = text[:ent['start']] + placeholder + text[ent['end']:]
82
+
83
+ return text, entity_map
84
+
85
+ except Exception as e:
86
+ st.error(f"Errore NER: {e}")
87
+ return text, {}
88
+
89
+ def anonymize(self, text: str) -> Tuple[str, Dict]:
90
+ """Pipeline completa di anonimizzazione"""
91
+ if not text or not text.strip():
92
+ return text, {}
93
+
94
+ # Regex prima, poi NER
95
+ masked_text, regex_entities = self.mask_with_regex(text)
96
+ final_text, ner_entities = self.mask_with_ner(masked_text)
97
+
98
+ # Combina entità
99
+ all_entities = {**regex_entities, **ner_entities}
100
+
101
+ return final_text, all_entities
src/config.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configurazioni per il sistema di anonimizzazione documenti.
3
+ """
4
+
5
+ import os
6
+ from dotenv import load_dotenv
7
+
8
+ # Carica variabili d'ambiente
9
+ load_dotenv()
10
+
11
+ class Config:
12
+ """Configurazione del sistema"""
13
+
14
+ # Modelli AI
15
+ NER_MODEL = "Davlan/bert-base-multilingual-cased-ner-hrl"
16
+
17
+ # Azure OpenAI
18
+ AZURE_ENDPOINT = os.getenv("AZURE_ENDPOINT")
19
+ AZURE_API_KEY = os.getenv("AZURE_API_KEY")
20
+ AZURE_EMBEDDING_ENDPOINT = os.getenv("AZURE_ENDPOINT_EMB", os.getenv("AZURE_ENDPOINT"))
21
+ AZURE_EMBEDDING_API_KEY = os.getenv("AZURE_API_KEY_EMB", os.getenv("AZURE_API_KEY"))
22
+ AZURE_API_VERSION = "2024-02-01"
23
+ DEPLOYMENT_NAME = "gpt-4o"
24
+ AZURE_EMBEDDING_DEPLOYMENT_NAME = "text-embedding-ada-002"
25
+
26
+ # Pattern regex per entità sensibili
27
+ REGEX_PATTERNS = {
28
+ "IBAN": r'\bIT\d{2}(?: ?[A-Z0-9]){11,30}\b',
29
+ "EMAIL": r'\b[\w\.-]+@[\w\.-]+\.\w{2,}\b',
30
+ "CF": r'\b[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]\b',
31
+ "CARD": r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b',
32
+ "PHONE": r'\b\+?[0-9\s\-\(\)]{8,15}\b'
33
+ }
34
+
35
+ # Configura OPENAI_API_KEY per compatibilità
36
+ if Config.AZURE_API_KEY:
37
+ os.environ["OPENAI_API_KEY"] = Config.AZURE_API_KEY
src/main.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ App principale Streamlit per l'anonimizzazione documenti.
3
+ """
4
+
5
+ import streamlit as st
6
+ import json
7
+ import pandas as pd
8
+ from ui_components import (
9
+ setup_page_config, display_sidebar, display_entity_editor,
10
+ display_file_preview, display_analysis_results, display_crewai_result,
11
+ display_progress_metrics, display_examples_section, create_download_button
12
+ )
13
+ from utils import (
14
+ init_session_state, process_uploaded_files, run_anonymization,
15
+ run_ai_analysis, build_rag_knowledge_base, export_results_json,
16
+ get_confirmed_docs_count, reset_document_state, add_chat_message,
17
+ add_crewai_result, clear_crewai_history
18
+ )
19
+
20
+ def main():
21
+ """Funzione principale dell'app"""
22
+
23
+ # Setup
24
+ setup_page_config()
25
+ init_session_state()
26
+
27
+ # Header
28
+ st.title("🔒 Anonimizzatore Documenti con NER, RAG e CrewAI")
29
+ st.markdown("---")
30
+
31
+ # Sidebar
32
+ display_sidebar()
33
+
34
+ # Main tabs
35
+ tab1, tab2, tab3, tab4, tab5 = st.tabs([
36
+ "📤 Upload",
37
+ "🔍 Anonimizzazione",
38
+ "📊 Analisi",
39
+ "💬 Chatbot RAG",
40
+ "🤖 CrewAI"
41
+ ])
42
+
43
+ # TAB 1: Upload
44
+ with tab1:
45
+ upload_tab()
46
+
47
+ # TAB 2: Anonimizzazione
48
+ with tab2:
49
+ anonymization_tab()
50
+
51
+ # TAB 3: Analisi
52
+ with tab3:
53
+ analysis_tab()
54
+
55
+ # TAB 4: RAG
56
+ with tab4:
57
+ rag_tab()
58
+
59
+ # TAB 5: CrewAI
60
+ with tab5:
61
+ crewai_tab()
62
+
63
+ def upload_tab():
64
+ """Tab per upload file"""
65
+ st.header("📤 Carica Documenti")
66
+
67
+ uploaded_files = st.file_uploader(
68
+ "Carica uno o più file .txt",
69
+ type=['txt'],
70
+ accept_multiple_files=True,
71
+ help="Seleziona i file di testo da anonimizzare"
72
+ )
73
+
74
+ if uploaded_files:
75
+ if process_uploaded_files(uploaded_files):
76
+ st.success(f"Caricati {len(uploaded_files)} file")
77
+ st.rerun()
78
+ else:
79
+ st.info("Nessun nuovo file caricato.")
80
+
81
+ # Mostra anteprima
82
+ st.subheader("📄 File caricati")
83
+ for filename, file_data in st.session_state.uploaded_files.items():
84
+ display_file_preview(filename, file_data['content'])
85
+
86
+ def anonymization_tab():
87
+ """Tab per anonimizzazione"""
88
+ st.header("🔍 Anonimizzazione e Revisione")
89
+
90
+ if not st.session_state.uploaded_files:
91
+ st.warning("⚠️ Carica prima alcuni documenti nella tab 'Upload'")
92
+ return
93
+
94
+ # Bottone anonimizzazione
95
+ if st.button("🚀 Avvia Anonimizzazione", type="primary"):
96
+ run_anonymization()
97
+ st.rerun()
98
+
99
+ # Mostra documenti anonimizzati
100
+ if st.session_state.anonymized_docs:
101
+ st.subheader("📝 Revisiona Documenti Anonimizzati")
102
+
103
+ for filename, doc_data in st.session_state.anonymized_docs.items():
104
+ with st.expander(
105
+ f"📄 {filename} {'✅' if doc_data['confirmed'] else '⏳'}",
106
+ expanded=not doc_data['confirmed']
107
+ ):
108
+
109
+ col1, col2 = st.columns(2)
110
+
111
+ # Testo originale
112
+ with col1:
113
+ st.write("**Testo Originale:**")
114
+ preview = doc_data['original'][:300]
115
+ if len(doc_data['original']) > 300:
116
+ preview += "..."
117
+
118
+ st.text_area(
119
+ "Originale",
120
+ value=preview,
121
+ height=200,
122
+ disabled=True,
123
+ key=f"orig_{filename}",
124
+ label_visibility="collapsed"
125
+ )
126
+
127
+ # Testo anonimizzato
128
+ with col2:
129
+ st.write("**Testo Anonimizzato:**")
130
+ edited_text = st.text_area(
131
+ "Anonimizzato (modificabile)",
132
+ value=doc_data['anonymized'],
133
+ height=200,
134
+ key=f"anon_{filename}",
135
+ label_visibility="collapsed"
136
+ )
137
+
138
+ # Aggiorna se modificato
139
+ if edited_text != doc_data['anonymized']:
140
+ st.session_state.anonymized_docs[filename]['anonymized'] = edited_text
141
+
142
+ # Editor entità
143
+ updated_entities = display_entity_editor(dict(doc_data['entities']), filename)
144
+
145
+ # Bottoni azione
146
+ col_confirm, col_reset = st.columns(2)
147
+
148
+ with col_confirm:
149
+ if st.button(f"✅ Conferma {filename}", key=f"confirm_{filename}"):
150
+ st.session_state.anonymized_docs[filename]['confirmed'] = True
151
+ st.session_state.anonymized_docs[filename]['entities'] = updated_entities
152
+ st.success(f"✅ {filename} confermato!")
153
+ st.session_state.vector_store_built = False
154
+ st.rerun()
155
+
156
+ with col_reset:
157
+ if st.button(f"🔄 Reset {filename}", key=f"reset_{filename}"):
158
+ reset_document_state(filename)
159
+ st.rerun()
160
+
161
+ # Statistiche progresso
162
+ display_progress_metrics()
163
+
164
+ def analysis_tab():
165
+ """Tab per analisi AI"""
166
+ st.header("📊 Analisi AI")
167
+
168
+ confirmed_docs = {k: v for k, v in st.session_state.anonymized_docs.items()
169
+ if v.get('confirmed', False)}
170
+
171
+ if not confirmed_docs:
172
+ st.warning("⚠️ Conferma prima alcuni documenti anonimizzati")
173
+ return
174
+
175
+ st.write(f"Documenti confermati pronti: **{len(confirmed_docs)}**")
176
+
177
+ if st.button("🤖 Avvia Analisi AI", type="primary"):
178
+ run_ai_analysis()
179
+
180
+ # Mostra risultati
181
+ if st.session_state.processed_docs:
182
+ st.subheader("📋 Risultati Analisi")
183
+
184
+ for filename, result in st.session_state.processed_docs.items():
185
+ display_analysis_results(filename, result)
186
+
187
+ # Download JSON
188
+ result_json = export_results_json({
189
+ 'filename': filename,
190
+ 'anonymized_text': result['anonymized_text'],
191
+ 'analysis': result['analysis'],
192
+ 'entities': result['entities'],
193
+ 'entities_count': result['entities_count']
194
+ }, f"analisi_{filename}")
195
+
196
+ create_download_button(
197
+ result_json,
198
+ f"analisi_{filename}.json",
199
+ f"💾 Scarica {filename}",
200
+ f"download_{filename}"
201
+ )
202
+
203
+ def rag_tab():
204
+ """Tab per RAG chatbot"""
205
+ st.header("💬 Chatta con i Documenti")
206
+
207
+ confirmed_docs = {k: v for k, v in st.session_state.anonymized_docs.items()
208
+ if v.get('confirmed', False)}
209
+
210
+ if not confirmed_docs:
211
+ st.warning("⚠️ Carica e conferma documenti per abilitare il chatbot")
212
+ return
213
+
214
+ # Costruisci knowledge base
215
+ if build_rag_knowledge_base():
216
+ st.info(f"Chatbot pronto per {len(confirmed_docs)} documenti")
217
+
218
+ # Mostra cronologia chat
219
+ for message in st.session_state.chat_history:
220
+ with st.chat_message(message["role"]):
221
+ st.markdown(message["content"])
222
+
223
+ # Input utente
224
+ if prompt := st.chat_input("Fai una domanda sui documenti..."):
225
+ # Aggiungi messaggio utente
226
+ add_chat_message("user", prompt)
227
+ with st.chat_message("user"):
228
+ st.markdown(prompt)
229
+
230
+ # Genera risposta
231
+ with st.chat_message("assistant"):
232
+ with st.spinner("Generando risposta..."):
233
+ response = st.session_state.rag_chatbot.answer_question(prompt)
234
+ st.markdown(response)
235
+
236
+ # Aggiungi risposta
237
+ add_chat_message("assistant", response)
238
+ else:
239
+ st.error("Impossibile costruire knowledge base. Verifica configurazione Azure.")
240
+
241
+ def crewai_tab():
242
+ """Tab per CrewAI"""
243
+ st.header("🤖 Analisi Multi-Agente CrewAI")
244
+
245
+ confirmed_docs = {k: v for k, v in st.session_state.anonymized_docs.items()
246
+ if v.get('confirmed', False)}
247
+
248
+ if not confirmed_docs:
249
+ st.warning("⚠️ Conferma documenti per abilitare CrewAI")
250
+ return
251
+
252
+ if not st.session_state.crewai_manager.agents:
253
+ st.error("❌ CrewAI non configurato. Verifica Azure OpenAI.")
254
+ return
255
+
256
+ # Assicura knowledge base
257
+ build_rag_knowledge_base()
258
+
259
+ st.success(f"🎯 CrewAI pronto per {len(confirmed_docs)} documenti")
260
+
261
+ # Configurazione analisi
262
+ st.subheader("⚙️ Configurazione Analisi")
263
+
264
+ col1, col2 = st.columns(2)
265
+
266
+ with col1:
267
+ analysis_type = st.selectbox(
268
+ "Tipo di Analisi",
269
+ options=["comprehensive", "document", "sentiment", "rag", "custom"],
270
+ format_func=lambda x: {
271
+ "comprehensive": "🔍 Analisi Comprensiva",
272
+ "document": "📄 Analisi Documentale",
273
+ "sentiment": "😊 Sentiment Analysis",
274
+ "rag": "🔍 Query RAG Avanzata",
275
+ "custom": "⚙️ Personalizzata"
276
+ }[x]
277
+ )
278
+
279
+ with col2:
280
+ if analysis_type == "custom":
281
+ selected_agents = st.multiselect(
282
+ "Agenti da utilizzare",
283
+ options=list(st.session_state.crewai_manager.agents.keys()),
284
+ default=["strategy_coordinator"],
285
+ format_func=lambda x: {
286
+ "document_analyst": "📄 Document Analyst",
287
+ "rag_specialist": "🔍 RAG Specialist",
288
+ "strategy_coordinator": "🎯 Strategy Coordinator",
289
+ "sentiment_analyst": "😊 Sentiment Analyst"
290
+ }.get(x, x)
291
+ )
292
+ else:
293
+ selected_agents = []
294
+
295
+ # Query input
296
+ st.subheader("❓ Query per l'Analisi")
297
+ query_input = st.text_area(
298
+ "Inserisci la tua domanda:",
299
+ placeholder="Es: Analizza i temi principali e identifica rischi operativi...",
300
+ height=100
301
+ )
302
+
303
+ # Istruzioni personalizzate
304
+ if analysis_type == "custom":
305
+ custom_instructions = st.text_area(
306
+ "Istruzioni Personalizzate:",
307
+ placeholder="Istruzioni specifiche per gli agenti...",
308
+ height=80
309
+ )
310
+ else:
311
+ custom_instructions = ""
312
+
313
+ # Bottoni
314
+ col_analyze, col_clear = st.columns(2)
315
+
316
+ with col_analyze:
317
+ if st.button("🚀 Avvia Analisi CrewAI", type="primary", disabled=not query_input.strip()):
318
+ if analysis_type == "custom" and not selected_agents:
319
+ st.error("Seleziona almeno un agente")
320
+ else:
321
+ # Esegui analisi
322
+ if analysis_type == "custom":
323
+ result = st.session_state.crewai_manager.create_custom_task(
324
+ query_input, selected_agents, custom_instructions
325
+ )
326
+ else:
327
+ result = st.session_state.crewai_manager.create_analysis_task(
328
+ query_input, analysis_type
329
+ )
330
+
331
+ # Salva risultato
332
+ add_crewai_result(query_input, analysis_type, result, selected_agents)
333
+ st.success("✅ Analisi CrewAI completata!")
334
+
335
+ with col_clear:
336
+ if st.button("🗑️ Pulisci Cronologia"):
337
+ clear_crewai_history()
338
+ st.success("Cronologia pulita!")
339
+ st.rerun()
340
+
341
+ # Mostra risultati
342
+ if st.session_state.crewai_history:
343
+ st.subheader("📋 Risultati Analisi CrewAI")
344
+
345
+ for i, analysis in enumerate(reversed(st.session_state.crewai_history)):
346
+ display_crewai_result(analysis, len(st.session_state.crewai_history) - i)
347
+
348
+ # Download
349
+ result_json = export_results_json(analysis, f"crewai_analysis_{i}")
350
+ create_download_button(
351
+ result_json,
352
+ f"crewai_analysis_{analysis['timestamp'].replace(':', '-').replace(' ', '_')}.json",
353
+ "💾 Scarica Risultato",
354
+ f"download_crewai_{i}"
355
+ )
356
+
357
+ # Esempi
358
+ display_examples_section()
359
+
360
+ if __name__ == "__main__":
361
+ main()
src/ui_components.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Componenti UI riutilizzabili per Streamlit.
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ from typing import Dict
8
+ from config import Config
9
+
10
+ def setup_page_config():
11
+ """Configura la pagina Streamlit"""
12
+ st.set_page_config(
13
+ page_title="Anonimizzatore Documenti",
14
+ page_icon="🔒",
15
+ layout="wide"
16
+ )
17
+
18
+ def display_sidebar():
19
+ """Mostra sidebar con configurazioni"""
20
+ with st.sidebar:
21
+ st.header("⚙️ Configurazione")
22
+
23
+ # Status Azure
24
+ if Config.AZURE_API_KEY and Config.AZURE_ENDPOINT:
25
+ st.success("✅ Azure OpenAI configurato")
26
+ st.info(f"Chat Model: {Config.DEPLOYMENT_NAME}")
27
+ st.info(f"Embedding Model: {Config.AZURE_EMBEDDING_DEPLOYMENT_NAME}")
28
+ else:
29
+ st.error("❌ Azure OpenAI non configurato")
30
+ st.write("Configura le variabili d'ambiente:")
31
+ st.code("""
32
+ AZURE_ENDPOINT=your_endpoint
33
+ AZURE_API_KEY=your_api_key
34
+ AZURE_ENDPOINT_EMB=your_embedding_endpoint
35
+ AZURE_API_KEY_EMB=your_embedding_api_key
36
+ """)
37
+
38
+ st.markdown("---")
39
+
40
+ # Statistiche documenti
41
+ if 'uploaded_files' in st.session_state and st.session_state.uploaded_files:
42
+ st.subheader("📊 Statistiche")
43
+ uploaded_count = len(st.session_state.uploaded_files)
44
+ anonymized_count = len(st.session_state.get('anonymized_docs', {}))
45
+ confirmed_count = sum(1 for doc in st.session_state.get('anonymized_docs', {}).values()
46
+ if doc.get('confirmed', False))
47
+
48
+ st.metric("File caricati", uploaded_count)
49
+ st.metric("Anonimizzati", anonymized_count)
50
+ st.metric("Confermati", confirmed_count)
51
+
52
+ if confirmed_count > 0:
53
+ if st.session_state.get('vector_store_built', False):
54
+ st.success("✅ Knowledge Base pronto")
55
+ else:
56
+ st.info("🔄 Knowledge Base da costruire")
57
+
58
+ st.markdown("---")
59
+
60
+ # Reset button
61
+ if st.button("🔄 Reset sessione"):
62
+ for key in list(st.session_state.keys()):
63
+ del st.session_state[key]
64
+ st.rerun()
65
+
66
+ def display_entity_editor(entities: Dict, doc_key: str):
67
+ """Editor per entità rilevate"""
68
+ if not entities:
69
+ st.info("Nessuna entità sensibile rilevata.")
70
+ return entities
71
+
72
+ st.subheader("🔍 Entità rilevate")
73
+ st.write("Verifica e modifica le entità sensibili:")
74
+
75
+ current_entities_list = list(entities.items())
76
+ updated_entities_dict = {}
77
+ deleted_placeholders = set()
78
+
79
+ for i, (placeholder, original_value) in enumerate(current_entities_list):
80
+ col1, col2, col3 = st.columns([2, 3, 1])
81
+
82
+ with col1:
83
+ st.write(f"**{placeholder}**")
84
+
85
+ with col2:
86
+ new_value = st.text_input(
87
+ "Valore originale",
88
+ value=original_value,
89
+ key=f"{doc_key}_{placeholder}_value_{i}"
90
+ )
91
+ updated_entities_dict[placeholder] = new_value
92
+
93
+ with col3:
94
+ if st.button("🗑️", key=f"{doc_key}_{placeholder}_delete_{i}", help="Rimuovi"):
95
+ deleted_placeholders.add(placeholder)
96
+
97
+ # Gestisci cancellazioni
98
+ if deleted_placeholders:
99
+ final_entities = {k: v for k, v in updated_entities_dict.items()
100
+ if k not in deleted_placeholders}
101
+ st.session_state.anonymized_docs[doc_key]['entities'] = final_entities
102
+
103
+ # Re-anonimizza testo
104
+ from anonymizer import NERAnonimizer
105
+ anonymizer = NERAnonimizer()
106
+ st.session_state.anonymized_docs[doc_key]['anonymized'], _ = anonymizer.anonymize(
107
+ st.session_state.anonymized_docs[doc_key]['original']
108
+ )
109
+ st.session_state.vector_store_built = False
110
+ st.rerun()
111
+
112
+ return updated_entities_dict
113
+
114
+ def display_file_preview(filename: str, content: str, max_chars: int = 500):
115
+ """Mostra anteprima file"""
116
+ with st.expander(f"📄 {filename} ({len(content)} caratteri)"):
117
+ preview_text = content[:max_chars]
118
+ if len(content) > max_chars:
119
+ preview_text += "..."
120
+
121
+ st.text_area(
122
+ "Contenuto",
123
+ value=preview_text,
124
+ height=150,
125
+ disabled=True,
126
+ key=f"preview_{filename}",
127
+ label_visibility="collapsed"
128
+ )
129
+
130
+ def display_analysis_results(filename: str, result: Dict):
131
+ """Mostra risultati analisi"""
132
+ with st.expander(f"📊 Analisi: {filename}"):
133
+ # Metriche
134
+ col1, col2, col3 = st.columns(3)
135
+ col1.metric("Caratteri testo", len(result['anonymized_text']))
136
+ col2.metric("Entità trovate", result['entities_count'])
137
+ col3.metric("Stato", "✅ Completato")
138
+
139
+ # Testo anonimizzato
140
+ st.subheader("📄 Testo Anonimizzato")
141
+ st.text_area(
142
+ "Testo processato",
143
+ value=result['anonymized_text'],
144
+ height=150,
145
+ disabled=True,
146
+ key=f"analysis_text_{filename}"
147
+ )
148
+
149
+ # Analisi AI
150
+ st.subheader("🤖 Analisi AI")
151
+ st.markdown(result['analysis'])
152
+
153
+ # Entità
154
+ if result['entities']:
155
+ st.subheader("🔍 Entità Anonimizzate")
156
+ entities_df = pd.DataFrame([
157
+ {
158
+ 'Placeholder': k,
159
+ 'Valore Originale': v,
160
+ 'Tipo': k.split('_')[0].replace('[', '')
161
+ }
162
+ for k, v in result['entities'].items()
163
+ ])
164
+ st.dataframe(entities_df, use_container_width=True)
165
+
166
+ def display_crewai_result(analysis: Dict, index: int):
167
+ """Mostra risultato analisi CrewAI"""
168
+ with st.expander(
169
+ f"🤖 Analisi {index}: {analysis['analysis_type'].upper()} - {analysis['timestamp']}"
170
+ ):
171
+ # Info header
172
+ col1, col2, col3 = st.columns(3)
173
+
174
+ with col1:
175
+ st.metric("Tipo Analisi", analysis['analysis_type'].capitalize())
176
+
177
+ with col2:
178
+ st.metric("Timestamp", analysis['timestamp'])
179
+
180
+ with col3:
181
+ agents_used = analysis.get('agents_used', 'auto')
182
+ if agents_used == 'auto':
183
+ agent_count = "Automatico"
184
+ elif isinstance(agents_used, list):
185
+ agent_count = f"{len(agents_used)} agenti"
186
+ else:
187
+ agent_count = str(agents_used)
188
+ st.metric("Agenti", agent_count)
189
+
190
+ # Query e risultato
191
+ st.subheader("❓ Query Originale")
192
+ st.info(analysis['query'])
193
+
194
+ st.subheader("🎯 Risultato Analisi")
195
+ st.markdown(analysis['result'])
196
+
197
+ def display_progress_metrics():
198
+ """Mostra metriche di progresso"""
199
+ if 'anonymized_docs' in st.session_state:
200
+ confirmed_count = sum(1 for doc in st.session_state.anonymized_docs.values()
201
+ if doc.get('confirmed', False))
202
+ total_count = len(st.session_state.anonymized_docs)
203
+
204
+ if total_count > 0:
205
+ st.metric(
206
+ "Progresso Conferme",
207
+ f"{confirmed_count}/{total_count}",
208
+ delta=f"{(confirmed_count/total_count)*100:.1f}%"
209
+ )
210
+
211
+ def display_examples_section():
212
+ """Mostra esempi di query CrewAI"""
213
+ with st.expander("💡 Esempi di Query per CrewAI"):
214
+ st.markdown("""
215
+ **Analisi Comprensiva:**
216
+ - "Fornisci un'analisi completa dei documenti identificando rischi, opportunità e raccomandazioni strategiche"
217
+ - "Analizza la comunicazione aziendale e suggerisci miglioramenti nella gestione clienti"
218
+
219
+ **Analisi Documentale:**
220
+ - "Classifica i documenti per tipologia e identifica pattern ricorrenti"
221
+ - "Analizza la struttura e organizzazione delle informazioni nei documenti"
222
+
223
+ **Sentiment Analysis:**
224
+ - "Valuta il sentiment generale nelle comunicazioni e identifica aree di miglioramento"
225
+ - "Analizza le emozioni e i trend nei feedback dei clienti"
226
+
227
+ **Query RAG Avanzata:**
228
+ - "Trova tutte le menzioni di problemi operativi e le relative soluzioni proposte"
229
+ - "Estrai informazioni su scadenze, deadline e milestone importanti"
230
+
231
+ **Personalizzata:**
232
+ - Combina agenti specifici per analisi mirate alle tue esigenze
233
+ """)
234
+
235
+ def create_download_button(data: str, filename: str, label: str, key: str):
236
+ """Crea bottone download con dati"""
237
+ st.download_button(
238
+ label=label,
239
+ data=data,
240
+ file_name=filename,
241
+ mime="application/json",
242
+ key=key
243
+ )
src/utils.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Funzioni utility e gestione stato sessione.
3
+ """
4
+
5
+ import streamlit as st
6
+ import json
7
+ import pandas as pd
8
+ from datetime import datetime
9
+ from anonymizer import NERAnonimizer
10
+ from ai_processor import AzureProcessor, RAGChatbot, CrewAIManager
11
+
12
+ def init_session_state():
13
+ """Inizializza stato sessione"""
14
+ if 'anonymizer' not in st.session_state:
15
+ st.session_state.anonymizer = NERAnonimizer()
16
+
17
+ if 'processor' not in st.session_state:
18
+ st.session_state.processor = AzureProcessor()
19
+
20
+ if 'rag_chatbot' not in st.session_state:
21
+ st.session_state.rag_chatbot = RAGChatbot()
22
+
23
+ if 'crewai_manager' not in st.session_state:
24
+ st.session_state.crewai_manager = CrewAIManager(st.session_state.rag_chatbot)
25
+
26
+ if 'uploaded_files' not in st.session_state:
27
+ st.session_state.uploaded_files = {}
28
+
29
+ if 'anonymized_docs' not in st.session_state:
30
+ st.session_state.anonymized_docs = {}
31
+
32
+ if 'processed_docs' not in st.session_state:
33
+ st.session_state.processed_docs = {}
34
+
35
+ if 'chat_history' not in st.session_state:
36
+ st.session_state.chat_history = []
37
+
38
+ if 'crewai_history' not in st.session_state:
39
+ st.session_state.crewai_history = []
40
+
41
+ if 'vector_store_built' not in st.session_state:
42
+ st.session_state.vector_store_built = False
43
+
44
+ def validate_file_upload(uploaded_file) -> bool:
45
+ """Valida file caricato"""
46
+ if not uploaded_file:
47
+ return False
48
+
49
+ # Controlla estensione
50
+ if not uploaded_file.name.endswith('.txt'):
51
+ st.error("Solo file .txt sono supportati")
52
+ return False
53
+
54
+ # Controlla dimensione (max 10MB)
55
+ if uploaded_file.size > 10 * 1024 * 1024:
56
+ st.error("File troppo grande (max 10MB)")
57
+ return False
58
+
59
+ return True
60
+
61
+ def process_uploaded_files(uploaded_files):
62
+ """Processa file caricati"""
63
+ new_files_uploaded = False
64
+
65
+ for file in uploaded_files:
66
+ if validate_file_upload(file) and file.name not in st.session_state.uploaded_files:
67
+ try:
68
+ content = file.read().decode('utf-8')
69
+ st.session_state.uploaded_files[file.name] = {
70
+ 'content': content,
71
+ 'size': len(content)
72
+ }
73
+ new_files_uploaded = True
74
+ except Exception as e:
75
+ st.error(f"Errore lettura file {file.name}: {e}")
76
+
77
+ if new_files_uploaded:
78
+ # Reset stato quando si caricano nuovi file
79
+ st.session_state.anonymized_docs = {}
80
+ st.session_state.processed_docs = {}
81
+ st.session_state.vector_store_built = False
82
+ st.session_state.chat_history = []
83
+ st.session_state.crewai_history = []
84
+ return True
85
+
86
+ return False
87
+
88
+ def run_anonymization():
89
+ """Esegue anonimizzazione su tutti i file"""
90
+ if not st.session_state.uploaded_files:
91
+ st.warning("Nessun file caricato")
92
+ return
93
+
94
+ progress_bar = st.progress(0)
95
+ total_files = len(st.session_state.uploaded_files)
96
+
97
+ for i, (filename, file_data) in enumerate(st.session_state.uploaded_files.items()):
98
+ progress_bar.progress((i + 1) / total_files, f"Processando {filename}...")
99
+
100
+ # Anonimizza
101
+ anonymized_text, entities = st.session_state.anonymizer.anonymize(file_data['content'])
102
+
103
+ st.session_state.anonymized_docs[filename] = {
104
+ 'original': file_data['content'],
105
+ 'anonymized': anonymized_text,
106
+ 'entities': entities,
107
+ 'confirmed': False
108
+ }
109
+
110
+ progress_bar.empty()
111
+ st.success("✅ Anonimizzazione completata!")
112
+ st.session_state.vector_store_built = False
113
+
114
+ def run_ai_analysis():
115
+ """Esegue analisi AI sui documenti confermati"""
116
+ confirmed_docs = {k: v for k, v in st.session_state.anonymized_docs.items()
117
+ if v.get('confirmed', False)}
118
+
119
+ if not confirmed_docs:
120
+ st.warning("Nessun documento confermato")
121
+ return
122
+
123
+ progress_bar = st.progress(0)
124
+
125
+ for i, (filename, doc_data) in enumerate(confirmed_docs.items()):
126
+ progress_bar.progress((i + 1) / len(confirmed_docs), f"Analizzando {filename}...")
127
+
128
+ # Analisi Azure
129
+ analysis = st.session_state.processor.process_document(doc_data['anonymized'])
130
+
131
+ st.session_state.processed_docs[filename] = {
132
+ 'anonymized_text': doc_data['anonymized'],
133
+ 'entities_count': len(doc_data['entities']),
134
+ 'analysis': analysis,
135
+ 'entities': doc_data['entities']
136
+ }
137
+
138
+ progress_bar.empty()
139
+ st.success("✅ Analisi completata!")
140
+
141
+ def build_rag_knowledge_base():
142
+ """Costruisce knowledge base RAG"""
143
+ confirmed_docs = {k: v for k, v in st.session_state.anonymized_docs.items()
144
+ if v.get('confirmed', False)}
145
+
146
+ if not confirmed_docs:
147
+ st.warning("Nessun documento confermato per RAG")
148
+ return False
149
+
150
+ if not st.session_state.vector_store_built:
151
+ with st.spinner("Costruendo knowledge base..."):
152
+ st.session_state.rag_chatbot.build_vector_store(confirmed_docs)
153
+ st.session_state.vector_store_built = True
154
+ return True
155
+
156
+ return True
157
+
158
+ def export_results_json(results: dict, filename_prefix: str) -> str:
159
+ """Esporta risultati in JSON"""
160
+ export_data = {
161
+ **results,
162
+ 'metadata': {
163
+ 'exported_at': datetime.now().isoformat(),
164
+ 'total_items': len(results) if isinstance(results, dict) else 1
165
+ }
166
+ }
167
+
168
+ return json.dumps(export_data, indent=2, ensure_ascii=False, default=str)
169
+
170
+ def get_confirmed_docs_count() -> int:
171
+ """Ritorna numero documenti confermati"""
172
+ if 'anonymized_docs' not in st.session_state:
173
+ return 0
174
+
175
+ return sum(1 for doc in st.session_state.anonymized_docs.values()
176
+ if doc.get('confirmed', False))
177
+
178
+ def reset_document_state(filename: str):
179
+ """Reset stato documento specifico"""
180
+ if filename in st.session_state.uploaded_files:
181
+ original_data = st.session_state.uploaded_files[filename]
182
+ anonymized_text, entities = st.session_state.anonymizer.anonymize(original_data['content'])
183
+
184
+ st.session_state.anonymized_docs[filename] = {
185
+ 'original': original_data['content'],
186
+ 'anonymized': anonymized_text,
187
+ 'entities': entities,
188
+ 'confirmed': False
189
+ }
190
+ st.session_state.vector_store_built = False
191
+
192
+ def add_chat_message(role: str, content: str):
193
+ """Aggiunge messaggio alla chat history"""
194
+ st.session_state.chat_history.append({
195
+ "role": role,
196
+ "content": content
197
+ })
198
+
199
+ def add_crewai_result(query: str, analysis_type: str, result: str, agents_used=None):
200
+ """Aggiunge risultato CrewAI alla history"""
201
+ analysis_result = {
202
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
203
+ "query": query,
204
+ "analysis_type": analysis_type,
205
+ "result": result,
206
+ "agents_used": agents_used if agents_used else "auto"
207
+ }
208
+
209
+ st.session_state.crewai_history.append(analysis_result)
210
+
211
+ def clear_chat_history():
212
+ """Pulisce cronologia chat"""
213
+ st.session_state.chat_history = []
214
+
215
+ def clear_crewai_history():
216
+ """Pulisce cronologia CrewAI"""
217
+ st.session_state.crewai_history = []
218
+
219
+ def get_system_stats() -> dict:
220
+ """Ritorna statistiche sistema"""
221
+ return {
222
+ 'uploaded_files': len(st.session_state.get('uploaded_files', {})),
223
+ 'anonymized_docs': len(st.session_state.get('anonymized_docs', {})),
224
+ 'confirmed_docs': get_confirmed_docs_count(),
225
+ 'processed_docs': len(st.session_state.get('processed_docs', {})),
226
+ 'chat_messages': len(st.session_state.get('chat_history', [])),
227
+ 'crewai_analyses': len(st.session_state.get('crewai_history', [])),
228
+ 'vector_store_ready': st.session_state.get('vector_store_built', False)
229
+ }
style.css CHANGED
@@ -1,28 +1,28 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
1
+ body {
2
+ padding: 2rem;
3
+ font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
+ }
5
+
6
+ h1 {
7
+ font-size: 16px;
8
+ margin-top: 0;
9
+ }
10
+
11
+ p {
12
+ color: rgb(107, 114, 128);
13
+ font-size: 15px;
14
+ margin-bottom: 10px;
15
+ margin-top: 5px;
16
+ }
17
+
18
+ .card {
19
+ max-width: 620px;
20
+ margin: 0 auto;
21
+ padding: 16px;
22
+ border: 1px solid lightgray;
23
+ border-radius: 16px;
24
+ }
25
+
26
+ .card p:last-child {
27
+ margin-bottom: 0;
28
+ }
tests/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc ADDED
Binary file (6.89 kB). View file
 
tests/__pycache__/test_anonymizer.cpython-313-pytest-8.4.1.pyc ADDED
Binary file (45.4 kB). View file
 
tests/__pycache__/test_config.cpython-313-pytest-8.4.1.pyc ADDED
Binary file (34.8 kB). View file
 
tests/__pycache__/test_utils.cpython-313-pytest-8.4.1.pyc ADDED
Binary file (40 kB). View file
 
tests/conftest.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configurazioni pytest e fixtures condivise.
3
+ """
4
+
5
+ import pytest
6
+ import os
7
+ import tempfile
8
+ from unittest.mock import Mock, patch
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Aggiungi src al path per import
13
+ sys.path.insert(0, r"Giorno_10\src")
14
+
15
+ @pytest.fixture
16
+ def sample_text():
17
+ """Testo di esempio per test"""
18
+ return """
19
+ Gentile Mario Rossi,
20
+
21
+ La contatto in merito alla fattura n. 12345.
22
+ Il suo codice fiscale RSSMRA80A01H501Z risulta corretto.
23
+
24
+ Per il pagamento può utilizzare:
25
+ IBAN: IT60 X054 2811 1010 0000 0123 456
26
+ Email: mario.rossi@example.com
27
+ Telefono: +39 333 1234567
28
+
29
+ Carta: 4532 1234 5678 9012
30
+
31
+ Cordiali saluti,
32
+ Ufficio Amministrazione
33
+ ACME SpA
34
+ """
35
+
36
+ @pytest.fixture
37
+ def sample_text_no_entities():
38
+ """Testo senza entità sensibili"""
39
+ return """
40
+ Questo è un documento di prova
41
+ che non contiene informazioni sensibili.
42
+
43
+ Solo testo normale per i test.
44
+ """
45
+
46
+ @pytest.fixture
47
+ def sample_empty_text():
48
+ """Testo vuoto"""
49
+ return ""
50
+
51
+ @pytest.fixture
52
+ def sample_entities():
53
+ """Entità di esempio per test"""
54
+ return {
55
+ "[PER_0]": "Mario Rossi",
56
+ "[CF_0]": "RSSMRA80A01H501Z",
57
+ "[IBAN_0]": "IT60 X054 2811 1010 0000 0123 456",
58
+ "[EMAIL_0]": "mario.rossi@example.com",
59
+ "[PHONE_0]": "+39 333 1234567",
60
+ "[CARD_0]": "4532 1234 5678 9012",
61
+ "[ORG_0]": "ACME SpA"
62
+ }
63
+
64
+ @pytest.fixture
65
+ def mock_azure_config():
66
+ """Mock configurazioni Azure"""
67
+ with patch.dict(os.environ, {
68
+ 'AZURE_ENDPOINT': 'https://test.openai.azure.com/',
69
+ 'AZURE_API_KEY': 'test-api-key',
70
+ 'AZURE_ENDPOINT_EMB': 'https://test-emb.openai.azure.com/',
71
+ 'AZURE_API_KEY_EMB': 'test-emb-key'
72
+ }):
73
+ yield
74
+
75
+ @pytest.fixture
76
+ def mock_azure_client():
77
+ """Mock client Azure OpenAI"""
78
+ mock_client = Mock()
79
+
80
+ # Mock response per chat completion
81
+ mock_response = Mock()
82
+ mock_response.choices = [Mock()]
83
+ mock_response.choices[0].message.content = "Test analysis result"
84
+
85
+ mock_client.chat.completions.create.return_value = mock_response
86
+
87
+ return mock_client
88
+
89
+ @pytest.fixture
90
+ def mock_ner_pipeline():
91
+ """Mock pipeline NER"""
92
+ mock_pipeline = Mock()
93
+
94
+ # Mock entità rilevate
95
+ mock_entities = [
96
+ {
97
+ 'entity_group': 'PER',
98
+ 'score': 0.9,
99
+ 'start': 8,
100
+ 'end': 19,
101
+ 'word': 'Mario Rossi'
102
+ },
103
+ {
104
+ 'entity_group': 'ORG',
105
+ 'score': 0.8,
106
+ 'start': 200,
107
+ 'end': 208,
108
+ 'word': 'ACME SpA'
109
+ }
110
+ ]
111
+
112
+ mock_pipeline.return_value = mock_entities
113
+ return mock_pipeline
114
+
115
+ @pytest.fixture
116
+ def temp_test_file():
117
+ """File temporaneo per test"""
118
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
119
+ f.write("Test content for file operations")
120
+ temp_path = f.name
121
+
122
+ yield temp_path
123
+
124
+ # Cleanup
125
+ if os.path.exists(temp_path):
126
+ os.unlink(temp_path)
127
+
128
+ @pytest.fixture
129
+ def mock_streamlit():
130
+ """Mock componenti Streamlit per test"""
131
+ with patch('streamlit.error') as mock_error, \
132
+ patch('streamlit.warning') as mock_warning, \
133
+ patch('streamlit.success') as mock_success, \
134
+ patch('streamlit.info') as mock_info, \
135
+ patch('streamlit.spinner') as mock_spinner:
136
+
137
+ # Spinner context manager
138
+ mock_spinner.return_value.__enter__ = Mock()
139
+ mock_spinner.return_value.__exit__ = Mock(return_value=None)
140
+
141
+ yield {
142
+ 'error': mock_error,
143
+ 'warning': mock_warning,
144
+ 'success': mock_success,
145
+ 'info': mock_info,
146
+ 'spinner': mock_spinner
147
+ }
148
+
149
+ @pytest.fixture
150
+ def sample_anonymized_docs():
151
+ """Documenti anonimizzati di esempio"""
152
+ return {
153
+ 'document1.txt': {
154
+ 'original': 'Documento con Mario Rossi e mario@email.com',
155
+ 'anonymized': 'Documento con [PER_0] e [EMAIL_0]',
156
+ 'entities': {
157
+ '[PER_0]': 'Mario Rossi',
158
+ '[EMAIL_0]': 'mario@email.com'
159
+ },
160
+ 'confirmed': True
161
+ },
162
+ 'document2.txt': {
163
+ 'original': 'Altro documento con ACME SpA',
164
+ 'anonymized': 'Altro documento con [ORG_0]',
165
+ 'entities': {
166
+ '[ORG_0]': 'ACME SpA'
167
+ },
168
+ 'confirmed': False
169
+ }
170
+ }
171
+
172
+ # Configurazioni pytest
173
+ def pytest_configure(config):
174
+ """Configurazione pytest"""
175
+ config.addinivalue_line(
176
+ "markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')"
177
+ )
178
+ config.addinivalue_line(
179
+ "markers", "integration: marks tests as integration tests"
180
+ )
181
+ config.addinivalue_line(
182
+ "markers", "azure: marks tests that require Azure credentials"
183
+ )
tests/pytest_ini.txt ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool:pytest]
2
+ # Configurazione pytest
3
+ testpaths = tests
4
+ python_files = test_*.py
5
+ python_classes = Test*
6
+ python_functions = test_*
7
+
8
+ # Markers personalizzati
9
+ markers =
10
+ slow: marks tests as slow (deselect with '-m "not slow"')
11
+ integration: marks tests as integration tests
12
+ azure: marks tests that require Azure credentials
13
+ unit: marks tests as unit tests
14
+ smoke: marks tests as smoke tests
15
+
16
+ # Opzioni di default
17
+ addopts =
18
+ --strict-markers
19
+ --strict-config
20
+ --verbose
21
+ --tb=short
22
+ --cov=.
23
+ --cov-report=term-missing
24
+ --cov-report=html:htmlcov
25
+ --cov-fail-under=80
26
+ -p no:warnings
27
+
28
+ # Filtri warning
29
+ filterwarnings =
30
+ ignore::UserWarning
31
+ ignore::DeprecationWarning
32
+ ignore::PendingDeprecationWarning
33
+
34
+ # Configurazione coverage
35
+ [coverage:run]
36
+ source = .
37
+ omit =
38
+ tests/*
39
+ venv/*
40
+ env/*
41
+ .venv/*
42
+ setup.py
43
+ conftest.py
44
+
45
+ [coverage:report]
46
+ exclude_lines =
47
+ pragma: no cover
48
+ def __repr__
49
+ if self.debug:
50
+ if settings.DEBUG
51
+ raise AssertionError
52
+ raise NotImplementedError
53
+ if 0:
54
+ if __name__ == .__main__.:
55
+ class .*\bProtocol\):
56
+ @(abc\.)?abstractmethod
tests/test_anonymizer.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test per sistema anonimizzazione.
3
+ """
4
+ import sys
5
+ import os
6
+
7
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
8
+
9
+ import pytest
10
+ from unittest.mock import Mock, patch
11
+ from anonymizer import NERAnonimizer
12
+
13
+ class TestNERAnonimizer:
14
+ """Test classe NERAnonimizer"""
15
+
16
+ def test_init(self):
17
+ """Test inizializzazione"""
18
+ anonymizer = NERAnonimizer()
19
+ assert anonymizer.regex_patterns is not None
20
+ assert anonymizer._ner_pipe is None
21
+
22
+ @patch('anonymizer.pipeline')
23
+ def test_ner_pipe_lazy_loading(self, mock_pipeline, mock_streamlit):
24
+ """Test lazy loading del modello NER"""
25
+ anonymizer = NERAnonimizer()
26
+
27
+ # Prima chiamata - dovrebbe caricare il modello
28
+ pipe = anonymizer.ner_pipe
29
+ assert mock_pipeline.called
30
+
31
+ # Seconda chiamata - dovrebbe usare cache
32
+ mock_pipeline.reset_mock()
33
+ pipe2 = anonymizer.ner_pipe
34
+ assert not mock_pipeline.called
35
+ assert pipe == pipe2
36
+
37
+ def test_mask_with_regex_basic(self, sample_text):
38
+ """Test mascheramento regex base"""
39
+ anonymizer = NERAnonimizer()
40
+
41
+ masked_text, entities = anonymizer.mask_with_regex(sample_text)
42
+
43
+ # Verifica che abbia trovato entità
44
+ assert len(entities) > 0
45
+
46
+ # Verifica che le entità siano nel formato corretto
47
+ for placeholder, original in entities.items():
48
+ assert placeholder.startswith('[')
49
+ assert placeholder.endswith(']')
50
+ assert '_' in placeholder
51
+ assert original in sample_text
52
+ assert placeholder in masked_text
53
+
54
+ def test_mask_with_regex_iban(self):
55
+ """Test mascheramento IBAN specifico"""
56
+ anonymizer = NERAnonimizer()
57
+ text = "Il mio IBAN è IT60 X054 2811 1010 0000 0123 456 per i pagamenti"
58
+
59
+ masked_text, entities = anonymizer.mask_with_regex(text)
60
+
61
+ # Dovrebbe trovare l'IBAN
62
+ iban_entities = [k for k in entities.keys() if k.startswith('[IBAN_')]
63
+ assert len(iban_entities) == 1
64
+
65
+ iban_placeholder = iban_entities[0]
66
+ assert entities[iban_placeholder] == "IT60 X054 2811 1010 0000 0123 456"
67
+ assert iban_placeholder in masked_text
68
+
69
+ def test_mask_with_regex_email(self):
70
+ """Test mascheramento email"""
71
+ anonymizer = NERAnonimizer()
72
+ text = "Contattami su mario.rossi@example.com o test@domain.co.uk"
73
+
74
+ masked_text, entities = anonymizer.mask_with_regex(text)
75
+
76
+ # Dovrebbe trovare 2 email
77
+ email_entities = [k for k in entities.keys() if k.startswith('[EMAIL_')]
78
+ assert len(email_entities) == 2
79
+
80
+ email_values = [entities[k] for k in email_entities]
81
+ assert "mario.rossi@example.com" in email_values
82
+ assert "test@domain.co.uk" in email_values
83
+
84
+ def test_mask_with_regex_cf(self):
85
+ """Test mascheramento codice fiscale"""
86
+ anonymizer = NERAnonimizer()
87
+ text = "Il codice fiscale è RSSMRA80A01H501Z"
88
+
89
+ masked_text, entities = anonymizer.mask_with_regex(text)
90
+
91
+ cf_entities = [k for k in entities.keys() if k.startswith('[CF_')]
92
+ assert len(cf_entities) == 1
93
+ assert entities[cf_entities[0]] == "RSSMRA80A01H501Z"
94
+
95
+ def test_mask_with_regex_empty_text(self, sample_empty_text):
96
+ """Test con testo vuoto"""
97
+ anonymizer = NERAnonimizer()
98
+
99
+ masked_text, entities = anonymizer.mask_with_regex(sample_empty_text)
100
+
101
+ assert masked_text == sample_empty_text
102
+ assert len(entities) == 0
103
+
104
+ def test_mask_with_regex_no_entities(self, sample_text_no_entities):
105
+ """Test con testo senza entità"""
106
+ anonymizer = NERAnonimizer()
107
+
108
+ masked_text, entities = anonymizer.mask_with_regex(sample_text_no_entities)
109
+
110
+ assert masked_text == sample_text_no_entities
111
+ assert len(entities) == 0
112
+
113
+ def test_mask_with_ner_success(self, mock_ner_pipeline, mock_streamlit):
114
+ """Test mascheramento NER con successo"""
115
+ anonymizer = NERAnonimizer()
116
+ anonymizer._ner_pipe = mock_ner_pipeline
117
+
118
+ text = "Mario Rossi lavora in ACME SpA"
119
+
120
+ masked_text, entities = anonymizer.mask_with_ner(text)
121
+
122
+ # Verifica chiamata al modello
123
+ assert mock_ner_pipeline.called
124
+
125
+ # Verifica entità trovate
126
+ assert len(entities) == 2
127
+ per_entities = [k for k in entities.keys() if k.startswith('[PER_')]
128
+ org_entities = [k for k in entities.keys() if k.startswith('[ORG_')]
129
+
130
+ assert len(per_entities) == 1
131
+ assert len(org_entities) == 1
132
+
133
+ def test_mask_with_ner_no_model(self, mock_streamlit):
134
+ """Test NER senza modello caricato"""
135
+ anonymizer = NERAnonimizer()
136
+ anonymizer._ner_pipe = None
137
+
138
+ text = "Mario Rossi lavora in ACME SpA"
139
+
140
+ masked_text, entities = anonymizer.mask_with_ner(text)
141
+
142
+ # Dovrebbe ritornare testo invariato
143
+ assert masked_text == text
144
+ assert len(entities) == 0
145
+
146
+ def test_mask_with_ner_low_confidence(self, mock_streamlit):
147
+ """Test NER con confidence bassa"""
148
+ anonymizer = NERAnonimizer()
149
+
150
+ # Mock con score basso
151
+ mock_pipe = Mock()
152
+ mock_pipe.return_value = [
153
+ {
154
+ 'entity_group': 'PER',
155
+ 'score': 0.3, # Sotto threshold (0.5)
156
+ 'start': 0,
157
+ 'end': 11,
158
+ 'word': 'Mario Rossi'
159
+ }
160
+ ]
161
+ anonymizer._ner_pipe = mock_pipe
162
+
163
+ text = "Mario Rossi"
164
+ masked_text, entities = anonymizer.mask_with_ner(text)
165
+
166
+ # Non dovrebbe mascherare con confidence bassa
167
+ assert masked_text == text
168
+ assert len(entities) == 0
169
+
170
+ def test_anonymize_complete_pipeline(self, sample_text, mock_ner_pipeline, mock_streamlit):
171
+ """Test pipeline completa di anonimizzazione"""
172
+ anonymizer = NERAnonimizer()
173
+ anonymizer._ner_pipe = mock_ner_pipeline
174
+
175
+ anonymized_text, all_entities = anonymizer.anonymize(sample_text)
176
+
177
+ # Verifica che sia diverso dall'originale
178
+ assert anonymized_text != sample_text
179
+
180
+ # Verifica che contenga placeholder
181
+ assert '[' in anonymized_text and ']' in anonymized_text
182
+
183
+ # Verifica che abbia trovato entità da entrambi i sistemi
184
+ assert len(all_entities) > 0
185
+
186
+ # Verifica mix di entità regex e NER
187
+ regex_entities = [k for k in all_entities.keys()
188
+ if any(k.startswith(f'[{t}_') for t in ['IBAN', 'EMAIL', 'CF', 'CARD', 'PHONE'])]
189
+ ner_entities = [k for k in all_entities.keys()
190
+ if any(k.startswith(f'[{t}_') for t in ['PER', 'ORG'])]
191
+
192
+ assert len(regex_entities) > 0 # Dovrebbe trovare entità regex
193
+ assert len(ner_entities) > 0 # Dovrebbe trovare entità NER
194
+
195
+ def test_anonymize_empty_text(self, sample_empty_text):
196
+ """Test anonimizzazione testo vuoto"""
197
+ anonymizer = NERAnonimizer()
198
+
199
+ anonymized_text, entities = anonymizer.anonymize(sample_empty_text)
200
+
201
+ assert anonymized_text == sample_empty_text
202
+ assert len(entities) == 0
203
+
204
+ def test_anonymize_preserves_structure(self, mock_streamlit):
205
+ """Test che l'anonimizzazione preservi la struttura del testo"""
206
+ anonymizer = NERAnonimizer()
207
+
208
+ text = """Documento importante
209
+
210
+ Dati cliente:
211
+ - Nome: Mario Rossi
212
+ - Email: mario@test.com
213
+
214
+ Fine documento."""
215
+
216
+ anonymized_text, entities = anonymizer.anonymize(text)
217
+
218
+ # Dovrebbe preservare newline e struttura
219
+ assert '\n' in anonymized_text
220
+ assert 'Documento importante' in anonymized_text
221
+ assert 'Fine documento.' in anonymized_text
222
+
223
+ def test_placeholder_uniqueness(self, sample_text, mock_ner_pipeline, mock_streamlit):
224
+ """Test che i placeholder siano unici"""
225
+ anonymizer = NERAnonimizer()
226
+ anonymizer._ner_pipe = mock_ner_pipeline
227
+
228
+ anonymized_text, entities = anonymizer.anonymize(sample_text)
229
+
230
+ # Tutti i placeholder dovrebbero essere unici
231
+ placeholders = list(entities.keys())
232
+ assert len(placeholders) == len(set(placeholders))
233
+
234
+ # Ogni placeholder dovrebbe apparire nel testo
235
+ for placeholder in placeholders:
236
+ assert placeholder in anonymized_text
237
+
238
+ class TestAnonymizerEdgeCases:
239
+ """Test casi limite"""
240
+
241
+ def test_already_masked_text(self, mock_streamlit):
242
+ """Test testo già parzialmente mascherato"""
243
+ anonymizer = NERAnonimizer()
244
+
245
+ text = "Contatta [EMAIL_0] per info su [CF_0]"
246
+ masked_text, entities = anonymizer.mask_with_regex(text)
247
+
248
+ # Non dovrebbe ri-mascherare placeholder esistenti
249
+ assert masked_text == text
250
+ assert len(entities) == 0
251
+
252
+ def test_overlapping_patterns(self, mock_streamlit):
253
+ """Test pattern che si sovrappongono"""
254
+ anonymizer = NERAnonimizer()
255
+
256
+ # Testo con potenziali sovrapposizioni
257
+ text = "Email test@domain.com nel sito https://test@domain.com"
258
+
259
+ masked_text, entities = anonymizer.mask_with_regex(text)
260
+
261
+ # Dovrebbe gestire correttamente le sovrapposizioni
262
+ assert len(entities) >= 1
263
+ assert all(placeholder in masked_text for placeholder in entities.keys())
264
+
265
+ def test_special_characters(self, mock_streamlit):
266
+ """Test caratteri speciali"""
267
+ anonymizer = NERAnonimizer()
268
+
269
+ text = "Email: test@domain.com; IBAN: IT60X05428111010000001234567!"
270
+
271
+ masked_text, entities = anonymizer.mask_with_regex(text)
272
+
273
+ # Dovrebbe trovare entità anche con caratteri speciali intorno
274
+ email_found = any('EMAIL' in k for k in entities.keys())
275
+ iban_found = any('IBAN' in k for k in entities.keys())
276
+
277
+ assert email_found
278
+ assert iban_found
tests/test_config.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test per configurazioni sistema.
3
+ """
4
+ import os
5
+ import sys
6
+
7
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
8
+
9
+ import pytest
10
+ import os
11
+ from unittest.mock import patch
12
+ from config import Config, REGEX_PATTERNS
13
+
14
+ class TestConfig:
15
+ """Test classe Config"""
16
+
17
+ def test_config_attributes_exist(self):
18
+ """Test che tutti gli attributi richiesti esistano"""
19
+ assert hasattr(Config, 'NER_MODEL')
20
+ assert hasattr(Config, 'AZURE_ENDPOINT')
21
+ assert hasattr(Config, 'AZURE_API_KEY')
22
+ assert hasattr(Config, 'DEPLOYMENT_NAME')
23
+
24
+ def test_ner_model_default(self):
25
+ """Test modello NER di default"""
26
+ assert Config.NER_MODEL == "Davlan/bert-base-multilingual-cased-ner-hrl"
27
+
28
+ def test_deployment_name_default(self):
29
+ """Test deployment name di default"""
30
+ assert Config.DEPLOYMENT_NAME == "gpt-4o"
31
+
32
+ def test_api_version_default(self):
33
+ """Test API version di default"""
34
+ assert Config.AZURE_API_VERSION == "2024-02-01"
35
+
36
+ @patch.dict(os.environ, {
37
+ 'AZURE_ENDPOINT': 'https://test.openai.azure.com/',
38
+ 'AZURE_API_KEY': 'test-key'
39
+ })
40
+ def test_azure_config_from_env(self):
41
+ """Test lettura configurazione da environment"""
42
+ # Reload config per leggere nuove env vars
43
+ import importlib
44
+ import config
45
+ importlib.reload(config)
46
+
47
+ assert config.Config.AZURE_ENDPOINT == 'https://test.openai.azure.com/'
48
+ assert config.Config.AZURE_API_KEY == 'test-key'
49
+
50
+ @patch.dict(os.environ, {}, clear=True)
51
+ def test_azure_config_missing(self):
52
+ """Test configurazione Azure mancante"""
53
+ import importlib
54
+ import config
55
+ importlib.reload(config)
56
+
57
+ assert config.Config.AZURE_ENDPOINT is None
58
+ assert config.Config.AZURE_API_KEY is None
59
+
60
+ def test_openai_api_key_set(self):
61
+ """Test che OPENAI_API_KEY sia settata se Azure disponibile"""
62
+ with patch.dict(os.environ, {'AZURE_API_KEY': 'test-key'}):
63
+ import importlib
64
+ import config
65
+ importlib.reload(config)
66
+
67
+ assert os.environ.get('OPENAI_API_KEY') == 'test-key'
68
+
69
+ class TestRegexPatterns:
70
+ """Test pattern regex"""
71
+
72
+ def test_regex_patterns_exist(self):
73
+ """Test che tutti i pattern esistano"""
74
+ required_patterns = ["IBAN", "EMAIL", "CF", "CARD", "PHONE"]
75
+
76
+ for pattern in required_patterns:
77
+ assert pattern in REGEX_PATTERNS
78
+ assert isinstance(REGEX_PATTERNS[pattern], str)
79
+ assert len(REGEX_PATTERNS[pattern]) > 0
80
+
81
+ def test_iban_pattern(self):
82
+ """Test pattern IBAN italiano"""
83
+ import re
84
+ pattern = re.compile(REGEX_PATTERNS["IBAN"])
85
+
86
+ # IBAN valido
87
+ assert pattern.search("IT60 X054 2811 1010 0000 0123 4567")
88
+ assert pattern.search("IT60X05428111010000001234567")
89
+
90
+ # IBAN invalido
91
+ assert not pattern.search("GB60 X054 2811 1010 0000 0123 456") # Non IT
92
+ assert not pattern.search("IT60 X054") # Troppo corto
93
+
94
+ def test_email_pattern(self):
95
+ """Test pattern email"""
96
+ import re
97
+ pattern = re.compile(REGEX_PATTERNS["EMAIL"])
98
+
99
+ # Email valide
100
+ assert pattern.search("test@example.com")
101
+ assert pattern.search("user.name@domain.co.uk")
102
+ assert pattern.search("test123@test-domain.org")
103
+
104
+ # Email invalide
105
+ assert not pattern.search("invalid-email")
106
+ assert not pattern.search("@domain.com")
107
+ assert not pattern.search("test@")
108
+
109
+ def test_cf_pattern(self):
110
+ """Test pattern codice fiscale"""
111
+ import re
112
+ pattern = re.compile(REGEX_PATTERNS["CF"])
113
+
114
+ # CF valido (formato)
115
+ assert pattern.search("RSSMRA80A01H501Z")
116
+ assert pattern.search("VRDLCU85D15F205W")
117
+
118
+ # CF invalido
119
+ assert not pattern.search("RSSMRA80A01H501") # Troppo corto
120
+ assert not pattern.search("rssmra80a01h501z") # Minuscolo
121
+ assert not pattern.search("123456789012345") # Solo numeri
122
+
123
+ def test_card_pattern(self):
124
+ """Test pattern carta di credito"""
125
+ import re
126
+ pattern = re.compile(REGEX_PATTERNS["CARD"])
127
+
128
+ # Carte valide (formato)
129
+ assert pattern.search("1234 5678 9012 3456")
130
+ assert pattern.search("1234-5678-9012-3456")
131
+ assert pattern.search("1234567890123456")
132
+
133
+ # Carte invalide
134
+ assert not pattern.search("1234 5678 9012") # Troppo corto
135
+ assert not pattern.search("abcd efgh ijkl mnop") # Lettere
136
+
137
+ def test_phone_pattern(self):
138
+ """Test pattern telefono"""
139
+ import re
140
+ pattern = re.compile(REGEX_PATTERNS["PHONE"])
141
+
142
+ # Telefoni validi
143
+ assert pattern.search("+39 333 1234567")
144
+ assert pattern.search("333-123-4567")
145
+ assert pattern.search("(02) 12345678")
146
+ assert pattern.search("3331234567")
147
+
148
+ # Telefoni invalidi
149
+ assert not pattern.search("123") # Troppo corto
150
+ assert not pattern.search("abc-def") # Lettere
151
+
152
+ class TestPatternValidation:
153
+ """Test validazione pattern"""
154
+
155
+ def test_all_patterns_compile(self):
156
+ """Test che tutti i pattern si compilino correttamente"""
157
+ import re
158
+
159
+ for name, pattern in REGEX_PATTERNS.items():
160
+ try:
161
+ re.compile(pattern)
162
+ except re.error:
163
+ pytest.fail(f"Pattern {name} non valido: {pattern}")
164
+
165
+ def test_patterns_not_empty(self):
166
+ """Test che nessun pattern sia vuoto"""
167
+ for name, pattern in REGEX_PATTERNS.items():
168
+ assert pattern.strip(), f"Pattern {name} è vuoto"
169
+
170
+ def test_patterns_have_word_boundaries(self):
171
+ """Test che i pattern usino word boundaries appropriati"""
172
+ for name, pattern in REGEX_PATTERNS.items():
173
+ # La maggior parte dei pattern dovrebbe avere \b per word boundary
174
+ if name in ["IBAN", "CF", "CARD", "PHONE"]:
175
+ assert "\\b" in pattern, f"Pattern {name} dovrebbe usare word boundaries"
tests/test_readme.md ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧪 Test Suite
2
+
3
+ Suite di test automatici per il sistema di anonimizzazione documenti.
4
+
5
+ ## 📋 Struttura Test
6
+
7
+ ```
8
+ tests/
9
+ ├── conftest.py # Fixtures e configurazioni pytest
10
+ ├── test_config.py # Test configurazioni sistema
11
+ ├── test_anonymizer.py # Test anonimizzazione NER+Regex
12
+ ├── test_ai_processor.py # Test componenti AI (Azure+RAG+CrewAI)
13
+ ├── test_utils.py # Test funzioni utility
14
+ ├── sample_data/ # Dati di test
15
+ └── README.md # Questa documentazione
16
+ ```
17
+
18
+ ## 🚀 Come Eseguire i Test
19
+
20
+ ### Setup Iniziale
21
+ ```bash
22
+ # Installa dipendenze test
23
+ pip install -r requirements-test.txt
24
+
25
+ # Installa dipendenze principali
26
+ pip install -r requirements.txt
27
+ ```
28
+
29
+ ### Esecuzione Base
30
+ ```bash
31
+ # Tutti i test
32
+ pytest
33
+
34
+ # Test specifico
35
+ pytest tests/test_anonymizer.py
36
+
37
+ # Test con coverage
38
+ pytest --cov
39
+
40
+ # Test veloci (escludi slow)
41
+ pytest -m "not slow"
42
+ ```
43
+
44
+ ### Esecuzione Avanzata
45
+ ```bash
46
+ # Test in parallelo
47
+ pytest -n auto
48
+
49
+ # Test con output dettagliato
50
+ pytest -v
51
+
52
+ # Test solo falliti
53
+ pytest --lf
54
+
55
+ # Test con benchmark
56
+ pytest --benchmark-only
57
+ ```
58
+
59
+ ## 🏷️ Markers Disponibili
60
+
61
+ ### **@pytest.mark.unit**
62
+ Test unitari veloci (<1s)
63
+ ```bash
64
+ pytest -m unit
65
+ ```
66
+
67
+ ### **@pytest.mark.integration**
68
+ Test di integrazione (<10s)
69
+ ```bash
70
+ pytest -m integration
71
+ ```
72
+
73
+ ### **@pytest.mark.slow**
74
+ Test lenti (>10s)
75
+ ```bash
76
+ pytest -m "not slow" # Escludi
77
+ pytest -m slow # Solo lenti
78
+ ```
79
+
80
+ ### **@pytest.mark.azure**
81
+ Test che richiedono Azure (con credenziali)
82
+ ```bash
83
+ pytest -m "not azure" # Senza Azure
84
+ ```
85
+
86
+ ## 🎯 Coverage Report
87
+
88
+ ### Generazione Report
89
+ ```bash
90
+ # HTML report
91
+ pytest --cov --cov-report=html
92
+ open htmlcov/index.html
93
+
94
+ # Terminal report
95
+ pytest --cov --cov-report=term-missing
96
+
97
+ # XML report (per CI/CD)
98
+ pytest --cov --cov-report=xml
99
+ ```
100
+
101
+ ### Target Coverage
102
+ - **Minimo**: 80% overall
103
+ - **Obiettivo**: 90%+ per moduli core
104
+ - **Critico**: 95%+ per anonimizzazione
105
+
106
+ ## 🧩 Test Categories
107
+
108
+ ### **Unit Tests (80%)**
109
+ - Funzioni singole isolate
110
+ - Mock dipendenze esterne
111
+ - Execution rapida
112
+
113
+ ### **Integration Tests (15%)**
114
+ - Componenti che interagiscono
115
+ - Mock servizi esterni (Azure)
116
+ - Execution media
117
+
118
+ ### **End-to-End Tests (5%)**
119
+ - Workflow completi
120
+ - Test con UI Streamlit
121
+ - Execution lenta
122
+
123
+ ## 📊 Test Data
124
+
125
+ ### **Fixtures Disponibili**
126
+ - `sample_text`: Documento con entità varie
127
+ - `sample_text_no_entities`: Testo pulito
128
+ - `sample_entities`: Mappa entità esempio
129
+ - `mock_azure_client`: Client Azure mockato
130
+ - `mock_ner_pipeline`: Pipeline NER mockato
131
+
132
+ ### **Sample Data**
133
+ ```
134
+ tests/sample_data/
135
+ ├── sample_document.txt # Doc normale con entità
136
+ ├── sample_with_entities.txt # Doc ricco di entità
137
+ └── sample_empty.txt # Doc vuoto
138
+ ```
139
+
140
+ ## 🔧 Configurazione
141
+
142
+ ### **pytest.ini**
143
+ Configurazione pytest con:
144
+ - Markers personalizzati
145
+ - Coverage settings
146
+ - Warning filters
147
+ - Default options
148
+
149
+ ### **conftest.py**
150
+ Fixtures condivise:
151
+ - Mock Azure OpenAI
152
+ - Mock Streamlit components
153
+ - Test data generators
154
+ - Environment setup
155
+
156
+ ## 🐛 Debugging Test
157
+
158
+ ### **Test Falliti**
159
+ ```bash
160
+ # Re-run solo falliti
161
+ pytest --lf
162
+
163
+ # Stop al primo fallimento
164
+ pytest -x
165
+
166
+ # Debug mode
167
+ pytest --pdb
168
+ ```
169
+
170
+ ### **Test Specifici**
171
+ ```bash
172
+ # Singolo test
173
+ pytest tests/test_anonymizer.py::TestNERAnonimizer::test_anonymize_complete_pipeline
174
+
175
+ # Classe di test
176
+ pytest tests/test_anonymizer.py::TestNERAnonimizer
177
+
178
+ # Con keyword
179
+ pytest -k "anonymize and not slow"
180
+ ```
181
+
182
+ ## 🚀 CI/CD Integration
183
+
184
+ ### **GitHub Actions Example**
185
+ ```yaml
186
+ - name: Run Tests
187
+ run: |
188
+ pytest --cov --cov-report=xml
189
+
190
+ - name: Upload Coverage
191
+ uses: codecov/codecov-action@v3
192
+ ```
193
+
194
+ ### **Pre-commit Hooks**
195
+ ```bash
196
+ # Installa pre-commit
197
+ pip install pre-commit
198
+ pre-commit install
199
+
200
+ # Test automatici prima del commit
201
+ pre-commit run --all-files
202
+ ```
203
+
204
+ ## 📈 Performance Testing
205
+
206
+ ### **Benchmark Tests**
207
+ ```bash
208
+ # Solo benchmark
209
+ pytest --benchmark-only
210
+
211
+ # Salva risultati
212
+ pytest --benchmark-save=baseline
213
+
214
+ # Confronta con baseline
215
+ pytest --benchmark-compare=baseline
216
+ ```
217
+
218
+ ### **Memory Testing**
219
+ ```bash
220
+ # Con memory profiler
221
+ pytest --memray
222
+
223
+ # Test memory leaks
224
+ pytest --memray --trace-memory
225
+ ```
226
+
227
+ ## 🔍 Quality Checks
228
+
229
+ ### **Code Quality**
230
+ ```bash
231
+ # Linting
232
+ flake8 .
233
+
234
+ # Formatting
235
+ black --check .
236
+
237
+ # Import sorting
238
+ isort --check-only .
239
+ ```
240
+
241
+ ### **Security Scanning**
242
+ ```bash
243
+ # Security vulnerabilities
244
+ bandit -r .
245
+
246
+ # Dependency check
247
+ safety check
248
+ ```
249
+
250
+ ## 📝 Writing New Tests
251
+
252
+ ### **Naming Convention**
253
+ - File: `test_<module>.py`
254
+ - Class: `Test<ComponentName>`
255
+ - Method: `test_<what_it_tests>`
256
+
257
+ ### **Test Structure**
258
+ ```python
259
+ def test_function_behavior():
260
+ # Arrange
261
+ input_data = "test input"
262
+ expected = "expected output"
263
+
264
+ # Act
265
+ result = function_under_test(input_data)
266
+
267
+ # Assert
268
+ assert result == expected
269
+ ```
270
+
271
+ ### **Best Practices**
272
+ - ✅ Test one thing per test
273
+ - ✅ Use descriptive names
274
+ - ✅ Mock external dependencies
275
+ - ✅ Test edge cases
276
+ - ✅ Keep tests independent
277
+
278
+ ## 🎪 Quick Commands
279
+
280
+ ```bash
281
+ # Full test suite
282
+ make test
283
+
284
+ # Fast tests only
285
+ make test-fast
286
+
287
+ # Coverage report
288
+ make coverage
289
+
290
+ # Quality checks
291
+ make lint
292
+
293
+ # All checks
294
+ make check-all
295
+ ```
tests/test_requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Test Dependencies
2
+ pytest>=7.4.0
3
+ pytest-cov>=4.1.0
4
+ pytest-mock>=3.11.0
5
+ pytest-xdist>=3.3.1
6
+
7
+ # Mocking and fixtures
8
+ responses>=0.23.0
9
+ factory-boy>=3.3.0
10
+
11
+ # Coverage reporting
12
+ coverage>=7.3.0
13
+
14
+ # Code quality
15
+ flake8>=6.0.0
16
+ black>=23.7.0
17
+ isort>=5.12.0
18
+
19
+ # Performance testing (optional)
20
+ pytest-benchmark>=4.0.0
21
+
22
+ # Async testing (if needed)
23
+ pytest-asyncio>=0.21.0
tests/test_utils.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test per funzioni utility.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+
8
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
9
+
10
+ import pytest
11
+ import json
12
+ import tempfile
13
+ import os
14
+ from unittest.mock import Mock, patch, MagicMock
15
+ from datetime import datetime
16
+ from utils import (
17
+ validate_file_upload, export_results_json, get_confirmed_docs_count,
18
+ add_chat_message, add_crewai_result, get_system_stats
19
+ )
20
+
21
+ class TestFileValidation:
22
+ """Test validazione file"""
23
+
24
+ def test_validate_file_upload_valid(self):
25
+ """Test file valido"""
26
+ mock_file = Mock()
27
+ mock_file.name = "test.txt"
28
+ mock_file.size = 1024 # 1KB
29
+
30
+ assert validate_file_upload(mock_file) == True
31
+
32
+ def test_validate_file_upload_none(self):
33
+ """Test file None"""
34
+ assert validate_file_upload(None) == False
35
+
36
+ @patch('streamlit.error')
37
+ def test_validate_file_upload_wrong_extension(self, mock_error):
38
+ """Test estensione file sbagliata"""
39
+ mock_file = Mock()
40
+ mock_file.name = "test.pdf"
41
+ mock_file.size = 1024
42
+
43
+ result = validate_file_upload(mock_file)
44
+
45
+ assert result == False
46
+ mock_error.assert_called_once()
47
+
48
+ @patch('streamlit.error')
49
+ def test_validate_file_upload_too_large(self, mock_error):
50
+ """Test file troppo grande"""
51
+ mock_file = Mock()
52
+ mock_file.name = "test.txt"
53
+ mock_file.size = 11 * 1024 * 1024 # 11MB
54
+
55
+ result = validate_file_upload(mock_file)
56
+
57
+ assert result == False
58
+ mock_error.assert_called_once()
59
+
60
+ class TestExportResults:
61
+ """Test export risultati"""
62
+
63
+ def test_export_results_json_basic(self):
64
+ """Test export JSON base"""
65
+ data = {"test": "value", "number": 123}
66
+
67
+ result = export_results_json(data, "test")
68
+
69
+ # Verifica che sia JSON valido
70
+ parsed = json.loads(result)
71
+ assert parsed["test"] == "value"
72
+ assert parsed["number"] == 123
73
+ assert "metadata" in parsed
74
+ assert "exported_at" in parsed["metadata"]
75
+
76
+ def test_export_results_json_with_datetime(self):
77
+ """Test export con datetime"""
78
+ data = {"timestamp": datetime.now()}
79
+
80
+ result = export_results_json(data, "test")
81
+
82
+ # Non dovrebbe lanciare errori
83
+ parsed = json.loads(result)
84
+ assert "timestamp" in parsed
85
+
86
+ def test_export_results_json_metadata(self):
87
+ """Test metadati export"""
88
+ data = {"item1": "value1", "item2": "value2"}
89
+
90
+ result = export_results_json(data, "test")
91
+ parsed = json.loads(result)
92
+
93
+ assert "metadata" in parsed
94
+ assert parsed["metadata"]["total_items"] == 2
95
+ assert "exported_at" in parsed["metadata"]
96
+
97
+ # Verifica formato ISO datetime
98
+ timestamp = parsed["metadata"]["exported_at"]
99
+ datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
100
+
101
+ class TestSessionStateHelpers:
102
+ """Test helper per session state"""
103
+
104
+ @patch('streamlit.session_state', {})
105
+ def test_get_confirmed_docs_count_empty(self):
106
+ """Test conteggio documenti confermati vuoto"""
107
+ result = get_confirmed_docs_count()
108
+ assert result == 0
109
+
110
+ @patch('streamlit.session_state')
111
+ def test_get_confirmed_docs_count_with_docs(self, mock_session):
112
+ """Test conteggio documenti confermati"""
113
+ mock_session.get.return_value = {
114
+ 'doc1': {'confirmed': True},
115
+ 'doc2': {'confirmed': False},
116
+ 'doc3': {'confirmed': True}
117
+ }
118
+
119
+ result = get_confirmed_docs_count()
120
+ assert result == 2
121
+
122
+ @patch('streamlit.session_state')
123
+ def test_add_chat_message(self, mock_session):
124
+ """Test aggiunta messaggio chat"""
125
+ mock_session.chat_history = []
126
+
127
+ add_chat_message("user", "Test message")
128
+
129
+ assert len(mock_session.chat_history) == 1
130
+ assert mock_session.chat_history[0]["role"] == "user"
131
+ assert mock_session.chat_history[0]["content"] == "Test message"
132
+
133
+ @patch('streamlit.session_state')
134
+ def test_add_crewai_result(self, mock_session):
135
+ """Test aggiunta risultato CrewAI"""
136
+ mock_session.crewai_history = []
137
+
138
+ add_crewai_result("test query", "comprehensive", "test result", ["agent1"])
139
+
140
+ assert len(mock_session.crewai_history) == 1
141
+ result = mock_session.crewai_history[0]
142
+
143
+ assert result["query"] == "test query"
144
+ assert result["analysis_type"] == "comprehensive"
145
+ assert result["result"] == "test result"
146
+ assert result["agents_used"] == ["agent1"]
147
+ assert "timestamp" in result
148
+
149
+ class TestSystemStats:
150
+ """Test statistiche sistema"""
151
+
152
+ @patch('streamlit.session_state')
153
+ def test_get_system_stats_empty(self, mock_session):
154
+ """Test statistiche sistema vuoto"""
155
+ mock_session.get.return_value = {}
156
+
157
+ stats = get_system_stats()
158
+
159
+ assert stats['uploaded_files'] == 0
160
+ assert stats['anonymized_docs'] == 0
161
+ assert stats['confirmed_docs'] == 0
162
+ assert stats['processed_docs'] == 0
163
+ assert stats['chat_messages'] == 0
164
+ assert stats['crewai_analyses'] == 0
165
+ assert stats['vector_store_ready'] == False
166
+
167
+ @patch('streamlit.session_state')
168
+ def test_get_system_stats_populated(self, mock_session):
169
+ """Test statistiche sistema con dati"""
170
+ def mock_get(key, default=None):
171
+ data = {
172
+ 'uploaded_files': {'file1': {}, 'file2': {}},
173
+ 'anonymized_docs': {
174
+ 'file1': {'confirmed': True},
175
+ 'file2': {'confirmed': False}
176
+ },
177
+ 'processed_docs': {'file1': {}},
178
+ 'chat_history': [{'role': 'user'}, {'role': 'assistant'}],
179
+ 'crewai_history': [{'query': 'test'}],
180
+ 'vector_store_built': True
181
+ }
182
+ return data.get(key, default)
183
+
184
+ mock_session.get.side_effect = mock_get
185
+
186
+ with patch('utils.get_confirmed_docs_count', return_value=1):
187
+ stats = get_system_stats()
188
+
189
+ assert stats['uploaded_files'] == 2
190
+ assert stats['anonymized_docs'] == 2
191
+ assert stats['confirmed_docs'] == 1
192
+ assert stats['processed_docs'] == 1
193
+ assert stats['chat_messages'] == 2
194
+ assert stats['crewai_analyses'] == 1
195
+ assert stats['vector_store_ready'] == True
196
+
197
+ class TestFileOperations:
198
+ """Test operazioni file"""
199
+
200
+ def test_temp_file_creation_and_cleanup(self, temp_test_file):
201
+ """Test creazione e cleanup file temporaneo"""
202
+ # File dovrebbe esistere
203
+ assert os.path.exists(temp_test_file)
204
+
205
+ # Contenuto dovrebbe essere corretto
206
+ with open(temp_test_file, 'r') as f:
207
+ content = f.read()
208
+ assert content == "Test content for file operations"
209
+
210
+ # Dopo il test, il file viene automaticamente rimosso dal fixture
211
+
212
+ class TestDataProcessing:
213
+ """Test elaborazione dati"""
214
+
215
+ def test_json_serialization_complex_data(self):
216
+ """Test serializzazione dati complessi"""
217
+ complex_data = {
218
+ "string": "test",
219
+ "number": 123,
220
+ "float": 45.67,
221
+ "boolean": True,
222
+ "null": None,
223
+ "list": [1, 2, 3],
224
+ "nested": {
225
+ "inner": "value"
226
+ },
227
+ "datetime": datetime.now()
228
+ }
229
+
230
+ result = export_results_json(complex_data, "complex")
231
+
232
+ # Dovrebbe serializzare senza errori
233
+ parsed = json.loads(result)
234
+ assert parsed["string"] == "test"
235
+ assert parsed["number"] == 123
236
+ assert parsed["float"] == 45.67
237
+ assert parsed["boolean"] == True
238
+ assert parsed["null"] is None
239
+ assert parsed["list"] == [1, 2, 3]
240
+ assert parsed["nested"]["inner"] == "value"
241
+ assert "datetime" in parsed # Convertito in stringa
242
+
243
+ class TestErrorHandling:
244
+ """Test gestione errori"""
245
+
246
+ @patch('streamlit.session_state', side_effect=Exception("Session state error"))
247
+ def test_get_confirmed_docs_count_exception(self):
248
+ """Test gestione eccezione in conteggio documenti"""
249
+ # Dovrebbe gestire l'eccezione e tornare 0
250
+ result = get_confirmed_docs_count()
251
+ assert result == 0
252
+
253
+ def test_export_results_with_non_serializable(self):
254
+ """Test export con oggetti non serializzabili"""
255
+ class NonSerializable:
256
+ pass
257
+
258
+ data = {"object": NonSerializable()}
259
+
260
+ # Dovrebbe gestire oggetti non serializzabili
261
+ result = export_results_json(data, "test")
262
+ parsed = json.loads(result)
263
+
264
+ # L'oggetto dovrebbe essere convertito in stringa
265
+ assert "object" in parsed
266
+
267
+ class TestValidationHelpers:
268
+ """Test helper di validazione"""
269
+
270
+ def test_validate_file_upload_edge_cases(self):
271
+ """Test casi limite validazione file"""
272
+ # File con nome vuoto
273
+ mock_file = Mock()
274
+ mock_file.name = ""
275
+ mock_file.size = 1024
276
+
277
+ with patch('streamlit.error'):
278
+ result = validate_file_upload(mock_file)
279
+ assert result == False
280
+
281
+ # File esattamente al limite (10MB)
282
+ mock_file.name = "test.txt"
283
+ mock_file.size = 10 * 1024 * 1024
284
+
285
+ result = validate_file_upload(mock_file)
286
+ assert result == True
287
+
288
+ # File con estensione maiuscola
289
+ mock_file.name = "test.TXT"
290
+ mock_file.size = 1024
291
+
292
+ result = validate_file_upload(mock_file)
293
+ assert result == True
294
+
295
+ class TestIntegrationHelpers:
296
+ """Test helper per integrazione"""
297
+
298
+ @patch('streamlit.session_state')
299
+ def test_session_state_integration(self, mock_session):
300
+ """Test integrazione con session state"""
301
+ # Simula stato iniziale
302
+ mock_session.chat_history = []
303
+ mock_session.crewai_history = []
304
+
305
+ # Aggiungi dati
306
+ add_chat_message("user", "Hello")
307
+ add_chat_message("assistant", "Hi there")
308
+ add_crewai_result("test query", "sentiment", "positive result")
309
+
310
+ # Verifica stato finale
311
+ assert len(mock_session.chat_history) == 2
312
+ assert len(mock_session.crewai_history) == 1
313
+
314
+ # Verifica contenuti
315
+ assert mock_session.chat_history[0]["role"] == "user"
316
+ assert mock_session.chat_history[1]["role"] == "assistant"
317
+ assert mock_session.crewai_history[0]["analysis_type"] == "sentiment"