creazione progetto
Browse files- .env +4 -0
- CHANGELOG.md +15 -0
- README.md +205 -10
- data/Mail.txt +23 -0
- data/contratto1.txt +37 -0
- data/contratto2.txt +52 -0
- data/contratto3.txt +29 -0
- data/email1.txt +11 -0
- data/email2.txt +23 -0
- data/email3.txt +20 -0
- data/email4.txt +34 -0
- data/fattura1.txt +44 -0
- data/fattura2.txt +34 -0
- data/hr_payslip.txt +11 -0
- data/legal_communication.txt +11 -0
- data/notifica.txt +1 -0
- data/report1.txt +75 -0
- data/report2.txt +56 -0
- docs/ARCHITECTURE.md +291 -0
- docs/INDEX.md +112 -0
- docs/PROMPT_TEMPLATE.md +198 -0
- docs/TECHNICAL_ANALYSIS.md +159 -0
- index.html +19 -19
- requirements.txt +9 -0
- src/__init__.py +0 -0
- src/__pycache__/ai_processor.cpython-313.pyc +0 -0
- src/__pycache__/anonymizer.cpython-313.pyc +0 -0
- src/__pycache__/config.cpython-313.pyc +0 -0
- src/__pycache__/utils.cpython-313.pyc +0 -0
- src/ai_processor.py +434 -0
- src/anonymizer.py +101 -0
- src/config.py +37 -0
- src/main.py +361 -0
- src/ui_components.py +243 -0
- src/utils.py +229 -0
- style.css +28 -28
- tests/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc +0 -0
- tests/__pycache__/test_anonymizer.cpython-313-pytest-8.4.1.pyc +0 -0
- tests/__pycache__/test_config.cpython-313-pytest-8.4.1.pyc +0 -0
- tests/__pycache__/test_utils.cpython-313-pytest-8.4.1.pyc +0 -0
- tests/conftest.py +183 -0
- tests/pytest_ini.txt +56 -0
- tests/test_anonymizer.py +278 -0
- tests/test_config.py +175 -0
- tests/test_readme.md +295 -0
- tests/test_requirements.txt +23 -0
- tests/test_utils.py +317 -0
.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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+

|
| 4 |
+

|
| 5 |
+

|
| 6 |
+

|
| 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"
|