GaetanoParente commited on
Commit
03a98ed
·
1 Parent(s): f1f081e

chore: ignore local model weights

Browse files
.gitattributes CHANGED
@@ -1,3 +1,4 @@
1
  *.keras filter=lfs diff=lfs merge=lfs -text
2
  *.h5 filter=lfs diff=lfs merge=lfs -text
3
  multi-classification-tokenizer.json filter=lfs diff=lfs merge=lfs -text
 
 
1
  *.keras filter=lfs diff=lfs merge=lfs -text
2
  *.h5 filter=lfs diff=lfs merge=lfs -text
3
  multi-classification-tokenizer.json filter=lfs diff=lfs merge=lfs -text
4
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -1 +1 @@
1
- __pycache__/
 
1
+ __pycache__/data/model/bpo_bert_model/*.safetensors
README.md CHANGED
@@ -1,213 +1,96 @@
1
  ---
2
  license: apache-2.0
3
  title: ngt-ai-platform
4
- sdk: docker
 
5
  colorFrom: blue
6
  colorTo: purple
7
  pinned: false
8
  ---
9
 
10
- # NGT AI Platform
11
 
12
- La piattaforma si propone di esporre i seguenti moduli:
13
- 1. binary classification di un testo fornito in input
14
- 2. image classification di una immagine fornita in input (Classi : Pneumonia, No_Pneumonia, Tubercolosi, No_Tubercolosi)
15
- 3. multilabel classification di un testo fornito in input (Classi: alt.atheism, comp.graphics, comp.os.ms-windows.misc, comp.sys.ibm.pc.hardware, comp.sys.mac.hardware, comp.windows.x, misc.forsale, rec.autos, rec.motorcycles, rec.sport.baseball, rec.sport.hockey, sci.crypt, sci.electronics, sci.med, sci.space, soc.religion.christian, talk.politics.guns, talk.politics.mideast, talk.politics.misc, talk.religion.misc)
16
 
 
17
 
18
- ## Required
19
 
20
- Prima di procedere è necessario installare anaconda utilizzando la seguente [guida](https://docs.anaconda.com/free/anaconda/install/linux/)
21
-
22
- La lemmatizzazione del testo viene eseguita con la libreria [spacy](https://spacy.io/usage).
23
- Procedere con i seguenti passaggi
24
-
25
- ```bash
26
- pip install -U pip setuptools wheel
27
- pip install -U spacy
28
- python -m spacy download it_core_news_lg
29
- ```
30
 
31
- Fondamentale installare anche la libreria tensorflow
32
 
33
- ```bash
34
- pip install tensorflow
35
- ```
36
-
37
- ## Run Locally
 
 
 
38
 
39
- Clona il progetto
 
 
 
40
 
41
- ```bash
42
- git clone git@github.com:gaeparente/ngt-ai-platform.git
43
- ```
 
44
 
45
- Installa il micro-framework Flask
46
 
47
- ```bash
48
- python -m pip install flask
49
- ```
50
 
51
- Installa libreria CORS di Flask
52
 
 
53
  ```bash
54
- pip install flask_cors
 
55
  ```
56
 
57
- Posizionati nella directory del file app.py
58
-
59
  ```bash
60
- cd ngt-ai-platform/
 
 
61
  ```
62
 
63
- Avvia il server
 
 
64
 
65
  ```bash
66
- flask run
67
  ```
 
68
 
69
- I moduli saranno quindi raggiungibili:
70
- 1. binary classification all'indirizzo http://127.0.0.1:5000/binary-classification
71
- 2. image classification all'indirizzo http://127.0.0.1:5000/image-classification
72
- 3. multilabel classification all'indirizzo http://127.0.0.1:5000/multi-classification
73
 
 
74
 
75
- ## Usage/Examples Binary classification
 
 
 
 
 
 
 
 
 
76
 
77
- Effettuare una chiamata POST all'indirizzo indicato in precedenza. Il body dovrà essere in formato form-data con le seguenti property:
78
- 1. text (required) -> contenente la sentence per cui si richiede la classificazione
79
- 2. model (optional) -> contenente il file del modello (.keras o .h5)
80
- 3. token (optional) -> contenente il file del tokenizer (.json)
81
 
82
- La risposta sarà quindi
83
 
84
- ```json
85
- {
86
- "lemma": "che posto ragazzo ! uno cucina ricercare in piccolo cortile di altro tempo . bello , buone , bravissimo . prenotare con largo anticipo .",
87
- "percent": "99.95895028114319",
88
- "sentiment": "POSITIVE"
89
- }
90
  ```
 
91
 
92
- ## Usage/Examples Image Classification
93
-
94
- Effettuare una chiamata POST all'indirizzo indicato in precedenza. Il body dovrà essere in formato form-data con le seguenti property:
95
- 1. image (required) -> contenente il file per cui si richiede la classificazione
96
- 2. model (optional) -> contenente il file del modello (.keras o .h5)
97
-
98
- La risposta sarà quindi
99
-
100
- ```json
101
- [
102
- {
103
- "classe": "Tubercolosi",
104
- "percent": "0.02414761"
105
- },
106
- {
107
- "classe": "No_Tubercolosi",
108
- "percent": "0.99304398"
109
- },
110
- {
111
- "classe": "Pneumonia",
112
- "percent": "0.00155318"
113
- },
114
- {
115
- "classe": "No_Pneumonia",
116
- "percent": "0.00484183"
117
- }
118
- ]
119
- ```
120
 
121
- ## Usage/Examples Multilabel classification
122
-
123
- Effettuare una chiamata POST all'indirizzo indicato in precedenza. Il body dovrà essere in formato form-data con le seguenti property:
124
- 1. text (required) -> contenente la sentence per cui si richiede la classificazione
125
- 2. model (optional) -> contenente il file del modello (.keras o .h5)
126
- 3. token (optional) -> contenente il file del tokenizer (.json)
127
-
128
- La risposta sarà quindi
129
-
130
- ```json
131
- [
132
- {
133
- "classe": "alt.atheism",
134
- "percent": "20.58875114"
135
- },
136
- {
137
- "classe": "comp.graphics",
138
- "percent": "5.57006039"
139
- },
140
- {
141
- "classe": "comp.os.ms-windows.misc",
142
- "percent": "1.00294100"
143
- },
144
- {
145
- "classe": "comp.sys.ibm.pc.hardware",
146
- "percent": "0.17852880"
147
- },
148
- {
149
- "classe": "comp.sys.mac.hardware",
150
- "percent": "0.24781623"
151
- },
152
- {
153
- "classe": "comp.windows.x",
154
- "percent": "3.20503265"
155
- },
156
- {
157
- "classe": "misc.forsale",
158
- "percent": "0.16137564"
159
- },
160
- {
161
- "classe": "rec.autos",
162
- "percent": "0.23865439"
163
- },
164
- {
165
- "classe": "rec.motorcycles",
166
- "percent": "0.35177895"
167
- },
168
- {
169
- "classe": "rec.sport.baseball",
170
- "percent": "1.18482364"
171
- },
172
- {
173
- "classe": "rec.sport.hockey",
174
- "percent": "0.21046386"
175
- },
176
- {
177
- "classe": "sci.crypt",
178
- "percent": "4.29985709"
179
- },
180
- {
181
- "classe": "sci.electronics",
182
- "percent": "2.09880602"
183
- },
184
- {
185
- "classe": "sci.med",
186
- "percent": "19.70048994"
187
- },
188
- {
189
- "classe": "sci.space",
190
- "percent": "5.71478717"
191
- },
192
- {
193
- "classe": "soc.religion.christian",
194
- "percent": "11.07885465"
195
- },
196
- {
197
- "classe": "talk.politics.guns",
198
- "percent": "1.57866161"
199
- },
200
- {
201
- "classe": "talk.politics.mideast",
202
- "percent": "1.79922581"
203
- },
204
- {
205
- "classe": "talk.politics.misc",
206
- "percent": "3.07453331"
207
- },
208
- {
209
- "classe": "talk.religion.misc",
210
- "percent": "17.71455258"
211
- }
212
- ]
213
- ```
 
1
  ---
2
  license: apache-2.0
3
  title: ngt-ai-platform
4
+ sdk: gradio
5
+ emoji: 🚀
6
  colorFrom: blue
7
  colorTo: purple
8
  pinned: false
9
  ---
10
 
11
+ # 🚀 NGT AI Platform
12
 
13
+ **NextGenTech AI Platform** è una suite modulare di Intelligenza Artificiale progettata per dimostrare capacità avanzate in ambito **NLP** (Natural Language Processing) e **Computer Vision**.
 
 
 
14
 
15
+ La piattaforma è costruita con un'architettura ibrida che integra modelli **TensorFlow/Keras** (Legacy) e **PyTorch/Transformers** (NextGenTech), il tutto esposto tramite un'interfaccia web interattiva basata su **Gradio**.
16
 
17
+ ![Platform UI](https://img.shields.io/badge/UI-Gradio-orange) ![Python](https://img.shields.io/badge/Python-3.10-blue) ![Framework](https://img.shields.io/badge/Hybrid-PyTorch%20%2B%20TensorFlow-purple)
18
 
19
+ ---
 
 
 
 
 
 
 
 
 
20
 
21
+ ## 🧩 Moduli Disponibili
22
 
23
+ ### 1. 🧩 BPO Intelligent Dispatcher (NextGen)
24
+ Un sistema avanzato per l'analisi dei ticket di assistenza clienti (Business Process Outsourcing).
25
+ * **Tecnologia:** DistilBERT (Fine-tuned) + spaCy (NER) + Custom Logic.
26
+ * **Funzionalità:**
27
+ * **Intent Classification:** Riconosce automaticamente se il ticket riguarda *Amministrazione*, *Supporto Tecnico* o *Rischio Churn*.
28
+ * **Smart Urgency:** Calcola la priorità basandosi sulla gravità del problema e sul tono del cliente.
29
+ * **Hybrid NER:** Estrae dati strutturati (Codici Cliente, Fatture, Email) usando un motore ibrido AI + Regex Contestuale.
30
+ * **Visualizzazione:** Rendering HTML dinamico delle entità estratte.
31
 
32
+ ### 2. 🩻 Healthcare Diagnostics (Computer Vision)
33
+ Moduli verticali per l'analisi di immagini mediche.
34
+ * **Chest X-Ray:** Classificazione di radiografie toraciche per individuare: *Polmonite*, *Tubercolosi* o *No Polmonite*, *No Tubercolosi*.
35
+ * **Diabetic Retinopathy:** Analisi del fondo oculare per rilevare segni di retinopatia diabetica.
36
 
37
+ ### 3. 📰 Legacy NLP Stack
38
+ Moduli classici di analisi testuale.
39
+ * **Topic Classification:** Classificazione multiclasse su 20 categorie di news (Dataset 20 Newsgroups).
40
+ * **Sentiment Analysis:** Analisi binaria (Positivo/Negativo) del tono del testo.
41
 
42
+ ---
43
 
44
+ ## 🛠️ Installazione
 
 
45
 
46
+ Il progetto richiede **Python 3.10**. Si consiglia l'uso di un virtual environment.
47
 
48
+ ### 1. Clona il repository
49
  ```bash
50
+ git clone git@github.com:gaeparente/ngt-ai-platform.git
51
+ cd ngt-ai-platform
52
  ```
53
 
54
+ ### 2. Setup dell'ambiente virtuale
 
55
  ```bash
56
+ python -m venv .venv
57
+ source .venv/bin/activate # Su Linux/Mac
58
+ # .venv\Scripts\activate # Su Windows
59
  ```
60
 
61
+ ### 3. Installazione Dipendenze
62
+
63
+ Il file requirements.txt è ottimizzato per installare le versioni CPU di PyTorch per risparmiare spazio.
64
 
65
  ```bash
66
+ pip install -r requirements.txt
67
  ```
68
+ Nota: Il sistema scaricherà automaticamente anche il modello linguistico italiano per spaCy (it_core_news_lg).
69
 
70
+ ### 📂 Struttura Cartelle e Modelli
 
 
 
71
 
72
+ Affinché la piattaforma funzioni, è necessario posizionare i modelli addestrati nella cartella corretta. Assicurati che la struttura sia la seguente:
73
 
74
+ ngt-ai-platform/
75
+ ├── app.py # Entry point dell'applicazione
76
+ ├── requirements.txt # Dipendenze
77
+ ├── modules/ # Logica di business
78
+ └── data/
79
+ ├── model/ # CARTELLA MODELLI (Non versionata)
80
+ │ ├── bpo_bert_model/ # Cartella del modello BERT addestrato
81
+ │ └── ... # modelli addestrati da noi
82
+ ├── gallery/ # Immagini di esempio per la Demo
83
+ └── tokenizer/ # tokenizer per la BinaryClassification e MultiClassification
84
 
85
+ ### 🚀 Avvio Piattaforma
 
 
 
86
 
87
+ Una volta installato tutto, avvia l'interfaccia web con:
88
 
89
+ ```bash
90
+ python app.py
 
 
 
 
91
  ```
92
+ L'applicazione sarà accessibile localmente all'indirizzo: 👉 http://127.0.0.1:7860
93
 
94
+ ### 📄 License
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ Distributed under the Apache 2.0 License.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,43 +1,10 @@
1
  import gradio as gr
2
  import cv2
3
- import os
4
-
5
  from modules.binary_classification import binary_classification as binary
6
  from modules.image_classification import image_classification as image
7
  from modules.multilabel_classification import multi_classification as multi
8
  from modules.retina import predict_diabetic_retinopathy as retina_detector
9
-
10
- # -------------------------------------------------------------
11
-
12
- def binary_classification(text):
13
- if text.strip():
14
- return binary(text)
15
- raise gr.Error('Il testo è obbligatorio!')
16
-
17
- def multi_classification(text):
18
- if text.strip():
19
- try:
20
- return multi(text)
21
- except Exception as e:
22
- raise gr.Error(f'Errore nel modello: {str(e)}')
23
- raise gr.Error('Il testo è obbligatorio!')
24
-
25
- def file_change(file):
26
- if isinstance(file, list):
27
- file = file[0]
28
- if file:
29
- return cv2.imread(file)
30
- return None
31
-
32
- def image_classification(img):
33
- if img is not None:
34
- return image(img)
35
- raise gr.Error('L\'immagine è obbligatoria!')
36
-
37
- def retina_classification(retina):
38
- if retina is not None:
39
- return retina_detector(retina)
40
- raise gr.Error('L\'immagine è obbligatoria!')
41
 
42
  # --- CONFIGURAZIONE TEMA ---
43
  theme = gr.themes.Soft(
@@ -61,12 +28,10 @@ custom_css = """
61
  padding-bottom: 20px;
62
  border-bottom: 1px solid #e2e8f0;
63
  }
64
-
65
- .logo-container img {
66
- margin-bottom: 4px !important;
67
- object-fit: contain;
68
  }
69
-
70
  .header-text-col h1 {
71
  font-family: 'Inter', sans-serif !important;
72
  font-weight: 900 !important;
@@ -79,221 +44,275 @@ custom_css = """
79
  padding-bottom: 0 !important;
80
  line-height: 1.0 !important;
81
  }
82
-
83
  .header-text-col .subheader {
84
- text-align: left !important;
85
- color: #64748b;
86
- font-size: 1.1em;
87
- font-weight: 500;
88
- margin-top: 0 !important;
89
- padding-top: 0 !important;
90
  }
91
 
92
  /* --- 2. CUSTOM TABS STYLE (DESKTOP) --- */
93
- .tabs > .tab-nav {
94
- border-bottom: none !important;
95
- gap: 8px !important;
96
- margin-bottom: 15px !important;
97
- }
98
-
99
  .tabs > .tab-nav > button {
100
- border: 1px solid #e5e7eb !important;
101
- border-radius: 10px !important;
102
- background-color: white;
103
- color: #475569 !important;
104
- font-weight: 600 !important;
105
- transition: all 0.2s ease-in-out;
106
- padding: 6px 16px !important;
107
- }
108
-
109
- .tabs > .tab-nav > button:hover {
110
- background-color: #f1f5f9 !important;
111
- transform: translateY(-1px);
112
  }
113
-
114
  .tabs > .tab-nav > button.selected {
115
  background: linear-gradient(135deg, #8B5CF6 0%, #D65DB1 100%) !important;
116
- color: white !important;
117
- border: 1px solid transparent !important;
118
  box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3) !important;
119
  }
120
 
121
- /* --- 3. PULSANTI PRIMARY --- */
122
  button.primary {
123
  background: linear-gradient(135deg, #8B5CF6 0%, #D65DB1 100%) !important;
124
- border: none !important;
125
- color: white !important;
126
- transition: filter 0.2s;
127
- }
128
- button.primary:hover {
129
- filter: brightness(1.1);
130
- box-shadow: 0 4px 15px rgba(139, 92, 246, 0.4);
131
  }
 
132
 
133
- /* --- FIX ALLINEAMENTO ALTEZZA (Desktop) --- */
134
- .fixed-height {
135
- height: 380px !important;
136
- overflow: hidden !important;
137
  }
138
- .fixed-height button,
139
- .fixed-height .image-container,
140
- .fixed-height .upload-container {
141
- height: 100% !important;
142
- max_height: 100% !important;
143
- min_height: 100% !important;
144
  }
145
- .fixed-height img {
146
- object-fit: contain !important;
147
- max_height: 100% !important;
148
  }
149
 
150
- /* --- 4. MOBILE RESPONSIVE (Aggiornato per i TAB) --- */
151
  @media (max-width: 768px) {
152
- /* Header Stack */
153
- .header-row {
154
- flex-direction: column !important;
155
- align-items: center !important;
156
- text-align: center !important;
157
- gap: 10px !important;
158
- }
159
- .header-text-col h1 {
160
- text-align: center !important;
161
- font-size: 1.8em !important;
162
- }
163
- .header-text-col .subheader {
164
- text-align: center !important;
165
- }
166
-
167
- /* Layout Moduli Stack */
168
- .responsive-row {
169
- flex-direction: column !important;
170
- display: flex !important;
171
- }
172
- .responsive-row > * {
173
- width: 100% !important;
174
- min-width: 100% !important;
175
- margin-bottom: 15px !important;
176
- }
177
-
178
- /* --- NUOVA GESTIONE TAB MOBILE --- */
179
- .tabs > .tab-nav {
180
- flex-wrap: wrap !important; /* Permette di andare a capo */
181
- justify-content: center !important; /* Centra i bottoni */
182
- gap: 6px !important; /* Spazio ridotto tra i bottoni */
183
- }
184
 
 
185
  .tabs > .tab-nav > button {
186
- flex-grow: 1 !important; /* Si allargano per riempire la riga */
187
- text-align: center !important;
188
- font-size: 0.85rem !important; /* Testo leggermente più piccolo */
189
- padding: 8px 10px !important; /* Padding ottimizzato per il tocco */
190
- margin: 0 !important;
191
- width: auto !important; /* Lascia decidere al flex-grow */
192
- min-width: 45% !important; /* Assicura che al massimo ci siano 2 tab per riga */
193
  }
194
  }
195
-
196
  footer {visibility: hidden}
197
  """
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  with gr.Blocks(theme=theme, css=custom_css, title="NGT AI Platform") as demo:
200
 
201
  # --- HEADER ---
202
  with gr.Row(elem_classes="header-row"):
203
  with gr.Column(scale=0, min_width=80, elem_classes="logo-container"):
204
- gr.Image(
205
- value="data/icon.png",
206
- show_label=False,
207
- show_download_button=False,
208
- show_share_button=False,
209
- container=False,
210
- show_fullscreen_button=False,
211
- interactive=False,
212
- height=80,
213
- width=80
214
- )
215
-
216
  with gr.Column(scale=1, elem_classes="header-text-col"):
217
- gr.Markdown("""
218
- <h1>NGT AI Platform</h1>
219
- <div class='subheader'>Advanced Machine Learning Solutions</div>
220
- """)
221
 
222
- # --- TAB 1: Chest X-Ray ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  with gr.Tab("🩻 Chest Diagnosis"):
224
  gr.Markdown("### 📥 Diagnostica Polmonare")
225
- gr.Markdown("Carica una radiografia o selezionane una dalla gallery per rilevare **Polmonite** o **Tubercolosi**.")
226
-
227
- # Aggiunta classe "responsive-row" e rimosso equal_height=True (lo gestiamo col CSS se serve)
228
  with gr.Row(elem_classes="responsive-row"):
229
  with gr.Column(scale=1):
230
  with gr.Accordion("📂 1. Seleziona da Gallery", open=True):
231
- file_selected = gr.FileExplorer(
232
- root_dir="data/gallery/xray",
233
- file_count='single',
234
- height=274 # Su desktop usa questo, su mobile il layout stackerà
235
- )
236
  with gr.Column(scale=1):
237
- image_input = gr.Image(type="numpy", height=320, label="2. Visualizzazione / Upload Manuale")
238
-
239
  with gr.Row():
240
  with gr.Column():
241
  analyze_btn_chest = gr.Button("🔍 Avvia Diagnosi Clinica", variant="primary", size="lg")
242
  image_output = gr.Label(num_top_classes=2, label="Risultato Predittivo")
243
-
244
  file_selected.change(file_change, inputs=file_selected, outputs=image_input)
245
  analyze_btn_chest.click(image_classification, inputs=image_input, outputs=image_output)
246
 
247
- # --- TAB 2: Diabetic Retinopathy ---
248
  with gr.Tab("👁️ Diabetic Retinopathy"):
249
  gr.Markdown("### 📥 Analisi Retinica")
250
- gr.Markdown("Deep Learning per la predizione della retinopatia diabetica.")
251
-
252
- # Aggiunta classe "responsive-row"
253
  with gr.Row(elem_classes="responsive-row"):
254
  with gr.Column(scale=1):
255
  with gr.Accordion("📂 1. Seleziona da Gallery", open=True):
256
- file_selected_dr = gr.FileExplorer(
257
- root_dir="data/gallery/retinopaty",
258
- file_count='single',
259
- height=274
260
- )
261
  with gr.Column(scale=1):
262
- image_input_dr = gr.Image(type="numpy", height=320, label="2. Visualizzazione / Upload Manuale")
263
-
264
  with gr.Row():
265
  with gr.Column():
266
  analyze_btn_dr = gr.Button("🔍 Analizza Retina", variant="primary", size="lg")
267
  with gr.Group():
268
  output_dr_label = gr.Label(label="Diagnosi Principale")
269
  output_dr_prob = gr.Label(label="Probabilità Patologia")
270
-
271
  file_selected_dr.change(file_change, inputs=file_selected_dr, outputs=image_input_dr)
272
  analyze_btn_dr.click(retina_classification, inputs=image_input_dr, outputs=[output_dr_label, output_dr_prob])
273
 
274
- # --- TAB 3: Review Classification ---
275
  with gr.Tab("📰 Topic Classification"):
276
  gr.Markdown("### Analisi Argomenti del Testo")
277
- with gr.Row():
 
278
  with gr.Column():
279
  multi_input = gr.Textbox(lines=5, placeholder="Incolla qui il testo...", label="Input")
280
  analyze_btn_multi = gr.Button("🏷️ Classifica", variant="primary")
 
281
  with gr.Column():
282
  multi_output = gr.Label(num_top_classes=5, label="Top Categorie")
283
-
284
- gr.Examples(examples=[["La NASA ha lanciato un nuovo satellite."], ["Il prezzo della GPU è sceso."]], inputs=multi_input)
285
  analyze_btn_multi.click(multi_classification, inputs=multi_input, outputs=multi_output)
286
 
287
- # --- TAB 4: Sentiment Analysis ---
288
  with gr.Tab("😊 Sentiment Analysis"):
289
  gr.Markdown("### Analisi del Sentiment")
290
- with gr.Row():
 
291
  with gr.Column():
292
  binary_input = gr.Textbox(lines=3, placeholder="Scrivi una recensione...", label="Input")
293
  analyze_btn_bin = gr.Button("⚖️ Analizza", variant="primary")
 
294
  with gr.Column():
295
  binary_output = gr.Label(label="Sentiment Score")
296
-
297
  analyze_btn_bin.click(binary_classification, inputs=binary_input, outputs=binary_output)
298
 
299
  if __name__ == "__main__":
 
1
  import gradio as gr
2
  import cv2
 
 
3
  from modules.binary_classification import binary_classification as binary
4
  from modules.image_classification import image_classification as image
5
  from modules.multilabel_classification import multi_classification as multi
6
  from modules.retina import predict_diabetic_retinopathy as retina_detector
7
+ from modules.bpo_dispatcher import predict_bpo_ticket
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  # --- CONFIGURAZIONE TEMA ---
10
  theme = gr.themes.Soft(
 
28
  padding-bottom: 20px;
29
  border-bottom: 1px solid #e2e8f0;
30
  }
31
+ .h4-margin{
32
+ margin-left: 5px;
 
 
33
  }
34
+ .logo-container img { margin-bottom: 4px !important; object-fit: contain; }
35
  .header-text-col h1 {
36
  font-family: 'Inter', sans-serif !important;
37
  font-weight: 900 !important;
 
44
  padding-bottom: 0 !important;
45
  line-height: 1.0 !important;
46
  }
 
47
  .header-text-col .subheader {
48
+ text-align: left !important; color: #64748b; font-size: 1.1em; font-weight: 500;
49
+ margin-top: 0 !important; padding-top: 0 !important;
 
 
 
 
50
  }
51
 
52
  /* --- 2. CUSTOM TABS STYLE (DESKTOP) --- */
53
+ .tabs > .tab-nav { border-bottom: none !important; gap: 8px !important; margin-bottom: 15px !important; }
 
 
 
 
 
54
  .tabs > .tab-nav > button {
55
+ border: 1px solid #e5e7eb !important; border-radius: 10px !important;
56
+ background-color: white; color: #475569 !important; font-weight: 600 !important;
57
+ padding: 6px 16px !important; transition: all 0.2s;
 
 
 
 
 
 
 
 
 
58
  }
59
+ .tabs > .tab-nav > button:hover { background-color: #f1f5f9 !important; transform: translateY(-1px); }
60
  .tabs > .tab-nav > button.selected {
61
  background: linear-gradient(135deg, #8B5CF6 0%, #D65DB1 100%) !important;
62
+ color: white !important; border: 1px solid transparent !important;
 
63
  box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3) !important;
64
  }
65
 
66
+ /* --- 3. COMPONENTS --- */
67
  button.primary {
68
  background: linear-gradient(135deg, #8B5CF6 0%, #D65DB1 100%) !important;
69
+ border: none !important; color: white !important; transition: filter 0.2s;
 
 
 
 
 
 
70
  }
71
+ button.primary:hover { filter: brightness(1.1); box-shadow: 0 4px 15px rgba(139, 92, 246, 0.4); }
72
 
73
+ .fixed-height { height: 380px !important; overflow: hidden !important; }
74
+ .fixed-height button, .fixed-height .image-container, .fixed-height .upload-container {
75
+ height: 100% !important; max_height: 100% !important; min_height: 100% !important;
 
76
  }
77
+ .fixed-height img { object-fit: contain !important; max_height: 100% !important; }
78
+
79
+ /* Stile per la Model Card nel tab BPO */
80
+ .model-card {
81
+ background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px;
82
+ font-size: 0.9em; color: #475569; margin-top: 10px;
83
  }
84
+
85
+ .model-card strong{
86
+ color: #475569 !important
87
  }
88
 
89
+ /* --- 4. MOBILE RESPONSIVE --- */
90
  @media (max-width: 768px) {
91
+ .header-row { flex-direction: column !important; align-items: center !important; text-align: center !important; gap: 10px !important; }
92
+ .header-text-col h1 { text-align: center !important; font-size: 1.8em !important; }
93
+ .header-text-col .subheader { text-align: center !important; }
94
+ .responsive-row { flex-direction: column !important; display: flex !important; }
95
+ .responsive-row > * { width: 100% !important; min-width: 100% !important; margin-bottom: 15px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ .tabs > .tab-nav { flex-wrap: wrap !important; justify-content: center !important; gap: 6px !important; }
98
  .tabs > .tab-nav > button {
99
+ flex-grow: 1 !important; text-align: center !important; font-size: 0.85rem !important;
100
+ padding: 8px 10px !important; margin: 0 !important; width: auto !important; min-width: 45% !important;
 
 
 
 
 
101
  }
102
  }
 
103
  footer {visibility: hidden}
104
  """
105
 
106
+ def binary_classification(text):
107
+ if text.strip(): return binary(text)
108
+ raise gr.Error('Il testo è obbligatorio!')
109
+
110
+ def multi_classification(text):
111
+ if text.strip():
112
+ try: return multi(text)
113
+ except Exception as e: raise gr.Error(f'Errore nel modello: {str(e)}')
114
+ raise gr.Error('Il testo è obbligatorio!')
115
+
116
+ def file_change(file):
117
+ if isinstance(file, list): file = file[0]
118
+ if file: return cv2.imread(file)
119
+ return None
120
+
121
+ def image_classification(img):
122
+ if img is not None: return image(img)
123
+ raise gr.Error('L\'immagine è obbligatoria!')
124
+
125
+ def retina_classification(retina):
126
+ if retina is not None: return retina_detector(retina)
127
+ raise gr.Error('L\'immagine è obbligatoria!')
128
+
129
+ def render_ner_html(entities):
130
+ """
131
+ Trasforma la lista [('testo', 'LABEL'), ('testo', None)] in HTML puro.
132
+ """
133
+ # Mappa colori HEX (più belli e moderni)
134
+ colors = {
135
+ "CODICE CLIENTE": "#3b82f6", # Blue 500
136
+ "N. FATTURA": "#f97316", # Orange 500
137
+ "COD. FORNITURA": "#d946ef", # Fuchsia 500
138
+ "EMAIL": "#ef4444", # Red 500
139
+ "TELEFONO": "#06b6d4", # Cyan 500
140
+ "PERSONA": "#22c55e", # Green 500
141
+ "AZIENDA": "#8b5cf6", # Violet 500
142
+ "LUOGO": "#64748b", # Slate 500
143
+ "POSSIBILE ID": "#a8a29e" # Stone 400
144
+ }
145
+
146
+ html = "<div style='line-height: 2.2; font-family: sans-serif; font-size: 16px; color: #334155; background-color: #1e293b'>"
147
+
148
+ for text, label in entities:
149
+ if label:
150
+ # Recupera colore o usa grigio di default
151
+ c = colors.get(label, "#1e293b")
152
+
153
+ # Crea lo "Chip" (Pillola colorata)
154
+ # - bg-color con opacità (c + '20')
155
+ # - border solido
156
+ # - label piccola in grassetto accanto al testo
157
+ html += f"""
158
+ <span style='background-color: {c}20; border: 1px solid {c}; border-radius: 6px; padding: 2px 6px; margin: 0 2px; white-space: nowrap;'>
159
+ <span style='font-size: 0.75em; font-weight: 700; color: {c}; text-transform: uppercase;'>{label}</span>
160
+ <span style='font-weight: 600; color: white; margin-left: 6px;'>{text}</span>
161
+ </span>
162
+ """
163
+ else:
164
+ # Testo normale
165
+ html += text.replace("\n", "<br>") # Gestisce a capo
166
+
167
+ html += "</div>"
168
+ return html
169
+
170
+ def bpo_dispatch_logic(text):
171
+ """
172
+ Funzione Ponte: Chiama il modulo AI e decide l'azione di business.
173
+ Restituisce un aggiornamento COMPLETO del componente NER per pulire la grafica.
174
+ """
175
+ try:
176
+ # 1. Chiamata al modello reale
177
+ intent, urgency, entities = predict_bpo_ticket(text)
178
+
179
+ if intent is None:
180
+ raise gr.Error("Errore nel modello BPO. Verifica i log.")
181
+
182
+ # 2. Logica di Business
183
+ top_intent = max(intent, key=intent.get)
184
+
185
+ action = "Inoltro generico"
186
+ if top_intent == "Retention / Churn Risk":
187
+ action = "🚨 ALERT: Assegnazione coda 'Retention' + Chiamata Outbound"
188
+ elif top_intent == "Supporto Tecnico":
189
+ action = "🛠️ Apertura Ticket JIRA (Livello 1) - Priorità Tecnica"
190
+ elif top_intent == "Amministrazione / Billing":
191
+ action = "💰 Verifica insoluti su SAP + Inoltro Backoffice Amm.vo"
192
+
193
+ html_output = render_ner_html(entities)
194
+
195
+ return intent, urgency, action, html_output
196
+
197
+ except Exception as e:
198
+ raise gr.Error(f"Errore nell'analisi: {str(e)}")
199
+
200
  with gr.Blocks(theme=theme, css=custom_css, title="NGT AI Platform") as demo:
201
 
202
  # --- HEADER ---
203
  with gr.Row(elem_classes="header-row"):
204
  with gr.Column(scale=0, min_width=80, elem_classes="logo-container"):
205
+ gr.Image(value="data/icon.png", show_label=False, show_download_button=False, show_share_button=False, container=False, show_fullscreen_button=False, interactive=False, height=80, width=80)
 
 
 
 
 
 
 
 
 
 
 
206
  with gr.Column(scale=1, elem_classes="header-text-col"):
207
+ gr.Markdown("""<h1>AI Platform</h1><div class='subheader'>Advanced Machine Learning Solutions</div>""")
 
 
 
208
 
209
+ # --- TAB 1: BPO INTELLIGENT DISPATCHER ---
210
+ with gr.Tab("🧩 BPO Dispatcher"):
211
+ gr.Markdown("### Intelligent Ticket Routing & NER")
212
+ gr.Markdown("Sistema proprietario per l'analisi automatica dei ticket di assistenza. Il modello identifica l'intento, l'urgenza e i dati sensibili del cliente.")
213
+
214
+ with gr.Row(elem_classes="responsive-row"):
215
+ # INPUT
216
+ with gr.Column(scale=1):
217
+ bpo_input = gr.Textbox(lines=8, placeholder="Incolla qui il contenuto della mail o del ticket...", label="Contenuto Ticket / Email")
218
+ analyze_btn_bpo = gr.Button("⚡ Analizza Richiesta", variant="primary")
219
+ gr.HTML("""
220
+ <div class='model-card'>
221
+ <strong>🛠️ Model Architecture:</strong> NGT-BERT-Custom (DistilBERT)<br>
222
+ <strong>📚 Training Data:</strong> Synthetic BPO Dataset (2025)<br>
223
+ <strong>🎯 Tasks:</strong> Intent Classification (Multi-class), Entity Extraction (NER)
224
+ </div>
225
+ """)
226
+
227
+ # OUTPUT
228
+ with gr.Column(scale=1):
229
+ with gr.Group():
230
+ gr.Markdown("#### 📋 Analisi Processata", elem_classes="h4-margin")
231
+ bpo_intent_output = gr.Label(num_top_classes=3, label="Intento Rilevato")
232
+ with gr.Row():
233
+ bpo_urgency_output = gr.Textbox(label="Livello Urgenza", scale=1)
234
+ bpo_action_output = gr.Textbox(label="Azione Consigliata (Auto)", scale=1)
235
+
236
+ gr.Markdown("#### 🔍 Dati Estratti (NER)", elem_classes="h4-margin")
237
+ bpo_ner_output = gr.HTML(label="Visualizzazione Entità")
238
+
239
+ gr.Examples(
240
+ examples=[
241
+ ["Buongiorno, vi scrivo perché la fattura n. 99283 del mese scorso è sbagliata. Non ho consumato così tanto. Il mio codice cliente è 4599201. Attendo rettifica urgente."],
242
+ ["Salve, il servizio non funziona da ieri. Mi dà errore 504 sul router. Risolvete subito per favore!"],
243
+ ["Vorrei disdire il contratto con decorrenza immediata se non mi risolvete il problema."]
244
+ ],
245
+ inputs=bpo_input
246
+ )
247
+
248
+ analyze_btn_bpo.click(
249
+ bpo_dispatch_logic,
250
+ inputs=bpo_input,
251
+ outputs=[bpo_intent_output, bpo_urgency_output, bpo_action_output, bpo_ner_output]
252
+ )
253
+
254
+ # --- TAB 2: Chest X-Ray ---
255
  with gr.Tab("🩻 Chest Diagnosis"):
256
  gr.Markdown("### 📥 Diagnostica Polmonare")
257
+ # INPUT
 
 
258
  with gr.Row(elem_classes="responsive-row"):
259
  with gr.Column(scale=1):
260
  with gr.Accordion("📂 1. Seleziona da Gallery", open=True):
261
+ file_selected = gr.FileExplorer(root_dir="data/gallery/xray", file_count='single', elem_classes=["fixed-height"])
 
 
 
 
262
  with gr.Column(scale=1):
263
+ image_input = gr.Image(type="numpy", label="2. Visualizzazione", elem_classes=["fixed-height"])
264
+ # OUTPUT
265
  with gr.Row():
266
  with gr.Column():
267
  analyze_btn_chest = gr.Button("🔍 Avvia Diagnosi Clinica", variant="primary", size="lg")
268
  image_output = gr.Label(num_top_classes=2, label="Risultato Predittivo")
 
269
  file_selected.change(file_change, inputs=file_selected, outputs=image_input)
270
  analyze_btn_chest.click(image_classification, inputs=image_input, outputs=image_output)
271
 
272
+ # --- TAB 3: Diabetic Retinopathy ---
273
  with gr.Tab("👁️ Diabetic Retinopathy"):
274
  gr.Markdown("### 📥 Analisi Retinica")
275
+ # INPUT
 
 
276
  with gr.Row(elem_classes="responsive-row"):
277
  with gr.Column(scale=1):
278
  with gr.Accordion("📂 1. Seleziona da Gallery", open=True):
279
+ file_selected_dr = gr.FileExplorer(root_dir="data/gallery/retinopaty", file_count='single', elem_classes=["fixed-height"])
 
 
 
 
280
  with gr.Column(scale=1):
281
+ image_input_dr = gr.Image(type="numpy", label="2. Visualizzazione", elem_classes=["fixed-height"])
282
+ # OUTPUT
283
  with gr.Row():
284
  with gr.Column():
285
  analyze_btn_dr = gr.Button("🔍 Analizza Retina", variant="primary", size="lg")
286
  with gr.Group():
287
  output_dr_label = gr.Label(label="Diagnosi Principale")
288
  output_dr_prob = gr.Label(label="Probabilità Patologia")
 
289
  file_selected_dr.change(file_change, inputs=file_selected_dr, outputs=image_input_dr)
290
  analyze_btn_dr.click(retina_classification, inputs=image_input_dr, outputs=[output_dr_label, output_dr_prob])
291
 
292
+ # --- TAB 4: Review Classification ---
293
  with gr.Tab("📰 Topic Classification"):
294
  gr.Markdown("### Analisi Argomenti del Testo")
295
+ with gr.Row(elem_classes="responsive-row"):
296
+ # INPUT
297
  with gr.Column():
298
  multi_input = gr.Textbox(lines=5, placeholder="Incolla qui il testo...", label="Input")
299
  analyze_btn_multi = gr.Button("🏷️ Classifica", variant="primary")
300
+ # OUTPUT
301
  with gr.Column():
302
  multi_output = gr.Label(num_top_classes=5, label="Top Categorie")
 
 
303
  analyze_btn_multi.click(multi_classification, inputs=multi_input, outputs=multi_output)
304
 
305
+ # --- TAB 5: Sentiment Analysis ---
306
  with gr.Tab("😊 Sentiment Analysis"):
307
  gr.Markdown("### Analisi del Sentiment")
308
+ with gr.Row(elem_classes="responsive-row"):
309
+ # INPUT
310
  with gr.Column():
311
  binary_input = gr.Textbox(lines=3, placeholder="Scrivi una recensione...", label="Input")
312
  analyze_btn_bin = gr.Button("⚖️ Analizza", variant="primary")
313
+ # OUTPUT
314
  with gr.Column():
315
  binary_output = gr.Label(label="Sentiment Score")
 
316
  analyze_btn_bin.click(binary_classification, inputs=binary_input, outputs=binary_output)
317
 
318
  if __name__ == "__main__":
data/model/bpo_bert_model/config.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "activation": "gelu",
3
+ "architectures": [
4
+ "DistilBertForSequenceClassification"
5
+ ],
6
+ "attention_dropout": 0.1,
7
+ "bos_token_id": null,
8
+ "dim": 768,
9
+ "dropout": 0.1,
10
+ "dtype": "float32",
11
+ "eos_token_id": null,
12
+ "hidden_dim": 3072,
13
+ "id2label": {
14
+ "0": "LABEL_0",
15
+ "1": "LABEL_1",
16
+ "2": "LABEL_2"
17
+ },
18
+ "initializer_range": 0.02,
19
+ "label2id": {
20
+ "LABEL_0": 0,
21
+ "LABEL_1": 1,
22
+ "LABEL_2": 2
23
+ },
24
+ "max_position_embeddings": 512,
25
+ "model_type": "distilbert",
26
+ "n_heads": 12,
27
+ "n_layers": 6,
28
+ "output_past": true,
29
+ "pad_token_id": 0,
30
+ "problem_type": "single_label_classification",
31
+ "qa_dropout": 0.1,
32
+ "seq_classif_dropout": 0.2,
33
+ "sinusoidal_pos_embds": false,
34
+ "tie_weights_": true,
35
+ "tie_word_embeddings": true,
36
+ "transformers_version": "5.0.0",
37
+ "use_cache": false,
38
+ "vocab_size": 119547
39
+ }
data/model/bpo_bert_model/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
data/model/bpo_bert_model/tokenizer_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "cls_token": "[CLS]",
4
+ "do_lower_case": false,
5
+ "is_local": false,
6
+ "mask_token": "[MASK]",
7
+ "model_max_length": 512,
8
+ "pad_token": "[PAD]",
9
+ "sep_token": "[SEP]",
10
+ "strip_accents": null,
11
+ "tokenize_chinese_chars": true,
12
+ "tokenizer_class": "DistilBertTokenizer",
13
+ "unk_token": "[UNK]"
14
+ }
modules/bpo_dispatcher.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification
3
+ import spacy
4
+ import re
5
+ import os
6
+ import torch.nn.functional as F
7
+
8
+ try:
9
+ from modules.binary_classification import binary_classification
10
+ except ImportError:
11
+ print("⚠️ Modulo sentiment non trovato. L'urgenza sarà basata solo sulle keyword.")
12
+ binary_classification = None
13
+
14
+ LABELS_MAP = {
15
+ 0: "Amministrazione / Billing",
16
+ 1: "Supporto Tecnico",
17
+ 2: "Retention / Churn Risk"
18
+ }
19
+
20
+ class BPODispatcher:
21
+ def __init__(self, model_path="data/model/bpo_bert_model"):
22
+ self.model = None
23
+ self.tokenizer = None
24
+ self.nlp = None
25
+ self.device = "cpu"
26
+
27
+ # 1. BERT
28
+ if os.path.exists(model_path):
29
+ try:
30
+ self.tokenizer = DistilBertTokenizerFast.from_pretrained(model_path)
31
+ self.model = DistilBertForSequenceClassification.from_pretrained(model_path)
32
+ self.model.to(self.device)
33
+ self.model.eval()
34
+ print("✅ Modello BERT caricato.")
35
+ except Exception as e:
36
+ print(f"❌ Errore BERT: {e}")
37
+
38
+ # 2. spaCy
39
+ try:
40
+ self.nlp = spacy.load("it_core_news_lg")
41
+ print("✅ spaCy caricato.")
42
+ except Exception as e:
43
+ print(f"❌ Errore spaCy: {e}")
44
+
45
+ def _extract_smart_entities(self, text):
46
+ entities = []
47
+ occupied_spans = [] # Tiene traccia delle zone di testo già etichettate
48
+
49
+ def is_overlapping(start, end):
50
+ """Controlla se la posizione è già occupata"""
51
+ for occ_start, occ_end in occupied_spans:
52
+ if (start < occ_end) and (end > occ_start):
53
+ return True
54
+ return False
55
+
56
+ def add_entity(text_val, label, start, end):
57
+ """Aggiunge l'entità solo se non si sovrappone e la registra"""
58
+ if not is_overlapping(start, end):
59
+ entities.append((text_val, label))
60
+ occupied_spans.append((start, end))
61
+
62
+ # --- FASE 1: REGEX ALTA PRIORITÀ (Dati Strutturati) ---
63
+
64
+ # A. EMAIL
65
+ for m in re.finditer(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', text):
66
+ add_entity(m.group(), "EMAIL", m.start(), m.end())
67
+
68
+ # B. TELEFONO (Mobile e Fisso Italiano)
69
+ # Cerca pattern tipo 3xx... o 0x... con spazi opzionali
70
+ for m in re.finditer(r'\b(?:3\d{2}|0\d{1,4})[\s.-]?\d{6,10}\b', text):
71
+ add_entity(m.group(), "TELEFONO", m.start(), m.end())
72
+
73
+ # C. NUMERI CONTESTUALI (Fatture, Clienti, Forniture)
74
+ # Regex migliorata: Accetta anche alfanumerici per codici cliente
75
+ # Pattern: Parola che inizia o finisce con cifra, lunga 4-15 chars
76
+ candidates = re.finditer(r'\b(?=[A-Za-z0-9]*\d)[A-Za-z0-9]{4,15}\b', text)
77
+
78
+ window_size = 35
79
+
80
+ for match in candidates:
81
+ val = match.group()
82
+ start, end = match.span()
83
+
84
+ # Se è già un telefono o mail, salta
85
+ if is_overlapping(start, end): continue
86
+
87
+ context = text[max(0, start - window_size):start].lower()
88
+
89
+ # 1. Fatture
90
+ if any(w in context for w in ["fattura", "bolletta", "nota", "nr.", "n."]):
91
+ # Verifica extra: le fatture solitamente sono solo numeri o hanno /
92
+ if val.isdigit() or '/' in val:
93
+ add_entity(val, "N. FATTURA", start, end)
94
+ continue
95
+
96
+ # 2. Forniture (POD/PDR/Luce/Gas)
97
+ if any(w in context for w in ["luce", "gas", "fornitura", "pod", "pdr", "contatore"]):
98
+ add_entity(val, "COD. FORNITURA", start, end)
99
+ continue
100
+
101
+ # 3. Codici Cliente (più generico, accetta alfanumerici)
102
+ if any(w in context for w in ["cliente", "codice", "utenza", "pratica", "id"]):
103
+ add_entity(val, "CODICE CLIENTE", start, end)
104
+ continue
105
+
106
+ # --- FASE 2: SPACY BASSA PRIORITÀ (Entità Semantiche) ---
107
+ if self.nlp:
108
+ doc = self.nlp(text)
109
+ for ent in doc.ents:
110
+ # VALIDAZIONE ANTI-ALLUCINAZIONE
111
+ # Regola: Una PERSONA non può contenere cifre
112
+ if ent.label_ == "PER":
113
+ if any(char.isdigit() for char in ent.text):
114
+ continue # Scarta "25458958" classificato come Persona
115
+ if len(ent.text) < 3:
116
+ continue # Scarta nomi troppo corti
117
+
118
+ add_entity(ent.text, "PERSONA", ent.start_char, ent.end_char)
119
+
120
+ elif ent.label_ == "ORG":
121
+ add_entity(ent.text, "AZIENDA", ent.start_char, ent.end_char)
122
+ return entities
123
+
124
+ def _calculate_smart_urgency(self, text, intent_label):
125
+ """
126
+ MATRICE DI URGENZA (Intent + Sentiment)
127
+ Combina la gravità del problema con lo stato d'animo del cliente.
128
+ """
129
+ urgency = "Bassa"
130
+
131
+ # 1. Analisi Sentiment (Se disponibile)
132
+ sentiment_score_neg = 0.0
133
+ if binary_classification:
134
+ try:
135
+ # binary_classification restituisce un dict {'POSITIVE': 0.x, 'NEGATIVE': 0.y}
136
+ sent_result = binary_classification(text)
137
+ sentiment_score_neg = sent_result.get('NEGATIVE', 0.0)
138
+ except Exception:
139
+ sentiment_score_neg = 0.5 # Fallback neutro
140
+
141
+ # 2. Matrice Decisionale
142
+
143
+ # CASO A: CHURN (Disdetta) -> Sempre Critico
144
+ if intent_label == "Retention / Churn Risk":
145
+ return "CRITICA (Rischio Abbandono)"
146
+
147
+ # CASO B: SUPPORTO TECNICO
148
+ elif intent_label == "Supporto Tecnico":
149
+ if sentiment_score_neg > 0.9: # Molto arrabbiato
150
+ return "ALTA (Tecnico + Cliente Furioso)"
151
+ elif "fermo" in text.lower() or "blocco" in text.lower():
152
+ return "ALTA (Fermo Servizio)"
153
+ else:
154
+ return "MEDIA (Guasto Standard)"
155
+
156
+ # CASO C: AMMINISTRAZIONE
157
+ elif intent_label == "Amministrazione / Billing":
158
+ if sentiment_score_neg > 0.95: # Furioso per i soldi
159
+ return "ALTA (Contestazione Aggressiva)"
160
+ elif "scadenza" in text.lower() or "stacco" in text.lower():
161
+ return "MEDIA (Rischio Amministrativo)"
162
+ else:
163
+ return "BASSA (Info / Richiesta)"
164
+
165
+ return urgency
166
+
167
+ def predict(self, text):
168
+ if self.model is None: return None, "Errore", []
169
+ if not text.strip(): return None, "Vuoto", []
170
+
171
+ # 1. Intent Classification (BERT)
172
+ inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=128, padding=True)
173
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
174
+ with torch.no_grad():
175
+ outputs = self.model(**inputs)
176
+ probs = F.softmax(outputs.logits, dim=-1)
177
+ label_output = {LABELS_MAP[i]: float(probs[0][i]) for i in range(len(LABELS_MAP))}
178
+
179
+ # Prendi l'intento vincente
180
+ top_idx = torch.max(probs, dim=-1)[1].item()
181
+ predicted_label = LABELS_MAP[top_idx]
182
+
183
+ # 2. Urgenza Intelligente (AI + Sentiment + Rules)
184
+ urgency = self._calculate_smart_urgency(text, predicted_label)
185
+
186
+ # 3. NER Extraction
187
+ entities = self._extract_smart_entities(text)
188
+
189
+ return label_output, urgency, entities
190
+
191
+ dispatcher = BPODispatcher()
192
+ def predict_bpo_ticket(text): return dispatcher.predict(text)
requirements.txt CHANGED
@@ -1,19 +1,34 @@
1
- # --- CORE LIBRARIES ---
2
- pydantic==2.10.6
3
- spacy==3.8.2
4
 
 
5
  gradio==4.44.1
 
6
 
7
- # --- LEGACY STACK (Non toccare) ---
 
8
  tensorflow==2.12.0
9
  numpy==1.23.5
10
  keras==2.12.0
11
  Keras-Preprocessing>=1.1.2
12
 
13
- # --- UTILITIES ---
 
 
 
 
 
 
 
 
14
  nltk>=3.8.1
 
 
 
15
  opencv-python-headless
16
- huggingface-hub==0.24.0
17
 
18
- # --- MODELS ---
 
 
19
  https://github.com/explosion/spacy-models/releases/download/it_core_news_lg-3.8.0/it_core_news_lg-3.8.0.tar.gz
 
1
+ # --- DIRETTIVE DI INSTALLAZIONE (Magia per CPU) ---
2
+ # Questa riga dice a pip di cercare le versioni CPU di Torch, evitando download giganti (CUDA)
3
+ --extra-index-url https://download.pytorch.org/whl/cpu
4
 
5
+ # --- CORE UI ---
6
  gradio==4.44.1
7
+ pydantic==2.10.6
8
 
9
+ # --- LEGACY STACK (NON TOCCARE - Vincoli rigidi) ---
10
+ # TensorFlow 2.12 richiede numpy < 1.24. Teniamo bloccato numpy a 1.23.5
11
  tensorflow==2.12.0
12
  numpy==1.23.5
13
  keras==2.12.0
14
  Keras-Preprocessing>=1.1.2
15
 
16
+ # --- Modulo BPO ---
17
+ # Grazie alla prima riga, scaricherà la versione CPU-only leggera
18
+ torch>=2.0.1
19
+ transformers>=4.35.0
20
+ accelerate>=0.25.0
21
+
22
+ # --- DATA & NLP UTILITIES ---
23
+ pandas>=2.0.0
24
+ spacy==3.8.2
25
  nltk>=3.8.1
26
+ scikit-learn>=1.3.0
27
+
28
+ # --- IMAGE PROCESSING ---
29
  opencv-python-headless
 
30
 
31
+ # --- MODELS & HUB ---
32
+ huggingface-hub==0.24.7
33
+ # Link diretto per scaricare il modello Spacy italiano
34
  https://github.com/explosion/spacy-models/releases/download/it_core_news_lg-3.8.0/it_core_news_lg-3.8.0.tar.gz