devusman commited on
Commit
e8fa023
·
verified ·
1 Parent(s): 05d3a8d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -141
app.py CHANGED
@@ -18,10 +18,7 @@ def load_it_model():
18
  Se nessun modello è installato, restituisce (None, None) e una istruzione per l'utente.
19
  """
20
  if spacy is None:
21
- return None, None, (
22
- "La libreria spaCy non è installata. "
23
- "Installa spaCy: pip install spacy"
24
- )
25
 
26
  candidates = ["it_core_news_lg", "it_core_news_md", "it_core_news_sm"]
27
  last_err = None
@@ -31,12 +28,10 @@ def load_it_model():
31
  return nlp, name, None
32
  except Exception as e:
33
  last_err = e
34
- # nessun modello trovato -> non fallare l'import, ma restituire messaggio utile
35
  suggestion = (
36
  "Impossibile caricare un modello italiano spaCy. "
37
  "Installa almeno uno tra: it_core_news_lg / it_core_news_md / it_core_news_sm.\n"
38
- "Esempio: python -m spacy download it_core_news_lg\n"
39
- f"Dettagli ultimo errore: {last_err}"
40
  )
41
  return None, None, suggestion
42
 
@@ -60,10 +55,7 @@ SPIEGAZIONI_POS_IT = {
60
  }
61
 
62
  SPIEGAZIONI_ENT_IT = {
63
- "PER": "Persona: Nomi di persone reali o fittizie.",
64
- "LOC": "Luogo: Nomi di luoghi geografici come paesi, città, stati.",
65
- "ORG": "Organizzazione: Nomi di aziende, istituzioni, governi.",
66
- "MISC": "Miscellanea: Entità che non rientrano nelle altre categorie (eventi, nazionalità, prodotti)."
67
  }
68
 
69
  # ------------------------------
@@ -73,22 +65,17 @@ KEY_MAP = {
73
  "Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo",
74
  "Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome",
75
  "Clitic": "Clitico", "Definite": "Definitezza", "Degree": "Grado",
76
- "Case": "Caso", "Poss": "Possessivo", "Reflex": "Riflessivo",
77
- "Aspect": "Aspetto", "Voice": "Voce",
78
  }
79
-
80
  VALUE_MAP = {
81
- "Masc": "Maschile", "Fem": "Femminile", "Sing": "Singolare", "Plur": "Plurale",
82
- "Cnd": "Condizionale", "Sub": "Congiuntivo", "Ind": "Indicativo", "Imp": "Imperfetto",
83
- "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio", "Fin": "Finita",
84
- "Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Pqp": "Trapassato",
85
- "1": "", "2": "", "3": "", "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo",
86
- "Dem": "Dimostrativo", "Art": "Articolativo", "Yes": "", "No": "No", "Def": "Determinato",
87
- "Indef": "Indefinito", "Abs": "Assoluto", "Cmp": "Comparativo", "Sup": "Superlativo",
88
- "Nom": "Nominativo", "Acc": "Accusativo", "Gen": "Genitivo", "Dat": "Dativo",
89
- "Perf": "Perfetto", "Prog": "Progressivo", "Act": "Attiva", "Pass": "Passiva",
90
  }
91
-
92
  PAIR_VALUE_MAP = {
93
  ("Mood", "Imp"): "Imperativo", ("Tense", "Imp"): "Imperfetto",
94
  ("Mood", "Ind"): "Indicativo", ("Definite", "Ind"): "Indeterminato",
@@ -124,25 +111,26 @@ MAPPA_DEP = {
124
  # ------------------------------
125
 
126
  def spiega_in_italiano(tag, tipo='pos'):
127
- if tipo == 'pos':
128
- return SPIEGAZIONI_POS_IT.get(tag, tag)
129
- return SPIEGAZIONI_ENT_IT.get(tag, tag)
130
 
131
  def traduci_morfologia(morph_str: str) -> str:
132
  if not morph_str or morph_str == "___": return "Non disponibile"
133
- parti_tradotte = []
134
- for parte in morph_str.split('|'):
 
135
  if '=' not in parte: continue
136
  chiave, valore = parte.split('=', 1)
137
  chiave_trad = KEY_MAP.get(chiave, chiave)
138
  valore_trad = PAIR_VALUE_MAP.get((chiave, valore), VALUE_MAP.get(valore, valore))
139
- parti_tradotte.append(f"{chiave_trad}: {valore_trad}")
140
- return ", ".join(sorted(list(set(parti_tradotte)))) or "Non disponibile"
141
-
142
 
143
  def ottieni_tipo_complemento_con_dettagli(token):
144
- preposizione = next((t.text.lower() for t in token.children if t.dep_ == "case"), None)
145
- if not preposizione: return MAPPA_DEP.get("obl")
 
146
 
147
  mappa = {
148
  "di": ("Complemento di Specificazione", "Risponde alla domanda: di chi? / di che cosa?"),
@@ -156,104 +144,75 @@ def ottieni_tipo_complemento_con_dettagli(token):
156
  "fra": ("Complemento Partitivo / Luogo", "Risponde alla domanda: fra chi? / fra cosa?"),
157
  }
158
 
159
- # Gestione preposizioni articolate
160
  for base, (label, desc) in mappa.items():
161
  if preposizione.startswith(base):
162
- label_final = label
163
- desc_final = desc
164
- # Check per complemento d'agente
165
  if base == "da" and any(c.dep_ == "aux:pass" for c in token.head.children):
166
- label_final = "Complemento d'Agente"
167
- desc_final = "Indica da chi è compiuta l'azione in una frase passiva."
168
- return {"label": label_final, "description": desc_final}
169
 
170
  return MAPPA_DEP.get("obl")
171
 
172
-
173
- def costruisci_sintagmi_con_dettagli(tokens_proposizione):
174
  """
175
- Costruisce una lista di componenti logici (sintagmi) da una lista di token spaCy.
176
- Questa versione è più precisa e robusta, evitando di raggruppare erroneamente i componenti.
177
  """
 
178
 
179
- def get_phrase_and_indices(token):
180
- """Costruisce il testo di un sintagma e restituisce gli indici dei token usati."""
181
- # Raccoglie ricorsivamente i token coordinati (es. "libri e quaderni")
182
- conjuncts = [c for c in token.children if c.dep_ == 'conj']
183
- tokens_nel_sintagma = [token]
184
- for conj in conjuncts:
185
- tokens_nel_sintagma.append(conj)
186
- # Aggiunge anche le congiunzioni (es. "e", "o")
187
- cc = next((c for c in conj.children if c.dep_ == 'cc'), None)
188
- if cc: tokens_nel_sintagma.append(cc)
189
-
190
- # Per ogni token principale, raccoglie i suoi modificatori diretti (articoli, aggettivi, etc.)
191
- all_phrase_tokens = []
192
- for t in tokens_nel_sintagma:
193
- subtree = list(t.subtree)
194
- # Filtra per tenere solo i modificatori strettamente legati
195
- modificatori = [n for n in subtree if n.head == t and n.dep_ in ('det', 'amod', 'advmod', 'case', 'compound', 'appos', 'nmod')]
196
- all_phrase_tokens.extend([t] + modificatori)
197
-
198
- # Assicura che la congiunzione sia inclusa se presente
199
- all_phrase_tokens.extend(c for c in token.children if c.dep_ == 'cc')
200
-
201
- # Ordina i token e crea la stringa finale
202
- tokens_ordinati = sorted(list(set(all_phrase_tokens)), key=lambda x: x.i)
203
- testo_sintagma = " ".join(t.text for t in tokens_ordinati)
204
- indici_usati = {t.i for t in tokens_ordinati}
205
-
206
- return testo_sintagma, indici_usati
207
 
 
 
 
 
208
  risultato_analisi = []
209
  indici_elaborati = set()
210
 
211
- # Token da non processare come "teste" di un sintagma (verranno inclusi dai loro "genitori")
212
- SKIP_DEPS = {'det', 'case', 'punct', 'aux', 'cop', 'mark', 'cc', 'aux:pass', 'amod', 'advmod'}
213
 
214
  for token in tokens_proposizione:
215
- if token.i in indici_elaborati or token.dep_ in SKIP_DEPS:
216
  continue
217
-
218
- # Gestione speciale per la copula in predicati nominali
219
- if token.dep_ == "ROOT" and any(c.dep_ == 'cop' for c in token.children):
220
- cop_token = next(c for c in token.children if c.dep_ == 'cop')
221
 
222
- # 1. Aggiungi il soggetto
223
- soggetto = next((s for s in token.head.children if s.dep_.startswith('nsubj')), None)
224
- if soggetto and soggetto.i not in indici_elaborati:
225
- s_text, s_indices = get_phrase_and_indices(soggetto)
226
- risultato_analisi.append({
227
- "text": s_text, "label_info": MAPPA_DEP['nsubj'],
228
- "token_details": {"lemma": soggetto.lemma_, "pos": spiega_in_italiano(soggetto.pos_), "morph": traduci_morfologia(str(soggetto.morph))}
229
- })
230
- indici_elaborati.update(s_indices)
231
-
232
- # 2. Aggiungi la copula
233
- risultato_analisi.append({
234
- "text": cop_token.text, "label_info": MAPPA_DEP['cop'],
235
- "token_details": {"lemma": cop_token.lemma_, "pos": spiega_in_italiano(cop_token.pos_), "morph": traduci_morfologia(str(cop_token.morph))}
236
- })
237
- indici_elaborati.add(cop_token.i)
238
-
239
- # 3. Aggiungi la parte nominale
240
- pn_text, pn_indices = get_phrase_and_indices(token)
241
- risultato_analisi.append({
242
- "text": pn_text, "label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."},
243
- "token_details": {"lemma": token.lemma_, "pos": spiega_in_italiano(token.pos_), "morph": traduci_morfologia(str(token.morph))}
244
- })
245
- indici_elaborati.update(pn_indices)
246
- continue
247
-
248
- # Logica standard per tutti gli altri componenti
249
- testo_sintagma, indici_usati = get_phrase_and_indices(token)
250
 
251
  dep = token.dep_
252
- if dep in ('obl', 'obl:agent'):
253
  info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
254
  else:
255
  info_etichetta = MAPPA_DEP.get(dep, {"label": dep.capitalize(), "description": "Relazione non mappata."})
256
 
 
 
 
 
257
  risultato_analisi.append({
258
  "text": testo_sintagma,
259
  "label_info": info_etichetta,
@@ -262,35 +221,43 @@ def costruisci_sintagmi_con_dettagli(tokens_proposizione):
262
  "pos": f"{token.pos_}: {spiega_in_italiano(token.pos_)}",
263
  "tag": f"{token.tag_}: {spiega_in_italiano(token.tag_)}",
264
  "morph": traduci_morfologia(str(token.morph))
265
- }
 
266
  })
267
  indici_elaborati.update(indici_usati)
268
 
269
- # Ordina i risultati in base alla loro apparizione nella frase
270
- risultato_analisi.sort(key=lambda x: x['text'].split()[0] in [t.text for t in tokens_proposizione] and [t.text for t in tokens_proposizione].index(x['text'].split()[0]))
 
 
 
 
 
 
 
 
 
 
271
  return risultato_analisi
272
 
273
- def analizza_proposizione_con_dettagli(token_proposizione):
274
- token_validi = [t for t in token_proposizione if not t.is_punct and not t.is_space]
275
- return costruisci_sintagmi_con_dettagli(token_validi)
276
 
277
  # ------------------------------
278
  # Routes
279
  # ------------------------------
280
  @app.route("/")
281
  def home():
282
- status = "ok" if nlp is not None else "model_missing"
283
  return jsonify({
284
- "messaggio": "API analisi logica in esecuzione",
285
- "modello_spacy": IT_MODEL if IT_MODEL else "Nessuno",
286
- "model_status": status,
287
- "model_error": MODEL_LOAD_ERROR,
288
- "endpoint": "/api/analyze"
289
  })
290
 
291
  @app.route('/api/analyze', methods=['POST'])
292
  def analizza_frase():
293
- if nlp is None:
294
  return jsonify({"errore": "Modello spaCy non caricato.", "dettagli": MODEL_LOAD_ERROR}), 503
295
 
296
  try:
@@ -301,22 +268,20 @@ def analizza_frase():
301
 
302
  doc = nlp(frase)
303
 
304
- proposizioni_subordinate = []
305
- indici_subordinate = set()
306
  SUBORD_DEPS = {"acl:relcl", "advcl", "ccomp", "csubj", "xcomp", "acl", "parataxis"}
307
 
308
  for token in doc:
309
- if token.dep_ in SUBORD_DEPS:
310
  subtree = list(token.subtree)
311
- subtree_indices = {t.i for t in subtree}
312
- if not indici_subordinate.intersection(subtree_indices):
313
- indici_subordinate.update(subtree_indices)
314
- info_tipo = MAPPA_DEP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Frase che dipende da un'altra."})
315
- proposizioni_subordinate.append({
316
- "type_info": info_tipo,
317
- "text": " ".join(t.text for t in subtree),
318
- "analysis": analizza_proposizione_con_dettagli(subtree)
319
- })
320
 
321
  token_principale = [t for t in doc if t.i not in indici_subordinate]
322
 
@@ -324,18 +289,16 @@ def analizza_frase():
324
  visti = set()
325
  for ent in doc.ents:
326
  if ent.text not in visti:
 
327
  entita_nominate.append({
328
- "text": ent.text,
329
- "label": ent.label_,
330
- "explanation": spiega_in_italiano(ent.label_, 'ent')
331
  })
332
- visti.add(ent.text)
333
 
334
  analisi_finale = {
335
- "full_sentence": frase,
336
- "model": IT_MODEL,
337
  "main_clause": {
338
- "text": " ".join(t.text for t in token_principale),
339
  "analysis": analizza_proposizione_con_dettagli(token_principale)
340
  },
341
  "subordinate_clauses": proposizioni_subordinate,
 
18
  Se nessun modello è installato, restituisce (None, None) e una istruzione per l'utente.
19
  """
20
  if spacy is None:
21
+ return None, None, ("La libreria spaCy non è installata. Installa spaCy: pip install spacy")
 
 
 
22
 
23
  candidates = ["it_core_news_lg", "it_core_news_md", "it_core_news_sm"]
24
  last_err = None
 
28
  return nlp, name, None
29
  except Exception as e:
30
  last_err = e
 
31
  suggestion = (
32
  "Impossibile caricare un modello italiano spaCy. "
33
  "Installa almeno uno tra: it_core_news_lg / it_core_news_md / it_core_news_sm.\n"
34
+ f"Esempio: python -m spacy download it_core_news_lg\nDettagli ultimo errore: {last_err}"
 
35
  )
36
  return None, None, suggestion
37
 
 
55
  }
56
 
57
  SPIEGAZIONI_ENT_IT = {
58
+ "PER": "Persona", "LOC": "Luogo", "ORG": "Organizzazione", "MISC": "Miscellanea"
 
 
 
59
  }
60
 
61
  # ------------------------------
 
65
  "Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo",
66
  "Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome",
67
  "Clitic": "Clitico", "Definite": "Definitezza", "Degree": "Grado",
68
+ "Case": "Caso", "Poss": "Possessivo", "Reflex": "Riflessivo", "Aspect": "Aspetto", "Voice": "Voce",
 
69
  }
 
70
  VALUE_MAP = {
71
+ "Masc": "Maschile", "Fem": "Femminile", "Sing": "Singolare", "Plur": "Plurale", "Cnd": "Condizionale",
72
+ "Sub": "Congiuntivo", "Ind": "Indicativo", "Imp": "Imperfetto", "Inf": "Infinito", "Part": "Participio",
73
+ "Ger": "Gerundio", "Fin": "Finita", "Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Pqp": "Trapassato",
74
+ "1": "", "2": "", "3": "", "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo",
75
+ "Art": "Articolativo", "Yes": "", "No": "No", "Def": "Determinato", "Indef": "Indefinito", "Abs": "Assoluto",
76
+ "Cmp": "Comparativo", "Sup": "Superlativo", "Nom": "Nominativo", "Acc": "Accusativo", "Gen": "Genitivo",
77
+ "Dat": "Dativo", "Perf": "Perfetto", "Prog": "Progressivo", "Act": "Attiva", "Pass": "Passiva",
 
 
78
  }
 
79
  PAIR_VALUE_MAP = {
80
  ("Mood", "Imp"): "Imperativo", ("Tense", "Imp"): "Imperfetto",
81
  ("Mood", "Ind"): "Indicativo", ("Definite", "Ind"): "Indeterminato",
 
111
  # ------------------------------
112
 
113
  def spiega_in_italiano(tag, tipo='pos'):
114
+ if tipo == 'pos': return SPIEGAZIONI_POS_IT.get(tag, tag)
115
+ if tipo == 'ent': return f"{SPIEGAZIONI_ENT_IT.get(tag, tag)}: {SPIEGAZIONI_ENT_IT.get(tag, {}).get('description', '')}"
116
+ return tag
117
 
118
  def traduci_morfologia(morph_str: str) -> str:
119
  if not morph_str or morph_str == "___": return "Non disponibile"
120
+ parti = morph_str.split('|')
121
+ parti_tradotte = set()
122
+ for parte in parti:
123
  if '=' not in parte: continue
124
  chiave, valore = parte.split('=', 1)
125
  chiave_trad = KEY_MAP.get(chiave, chiave)
126
  valore_trad = PAIR_VALUE_MAP.get((chiave, valore), VALUE_MAP.get(valore, valore))
127
+ parti_tradotte.add(f"{chiave_trad}: {valore_trad}")
128
+ return ", ".join(sorted(list(parti_tradotte))) or "Non disponibile"
 
129
 
130
  def ottieni_tipo_complemento_con_dettagli(token):
131
+ case_token = next((child for child in token.children if child.dep_ == 'case'), None)
132
+ if not case_token: return MAPPA_DEP.get("obl")
133
+ preposizione = case_token.text.lower()
134
 
135
  mappa = {
136
  "di": ("Complemento di Specificazione", "Risponde alla domanda: di chi? / di che cosa?"),
 
144
  "fra": ("Complemento Partitivo / Luogo", "Risponde alla domanda: fra chi? / fra cosa?"),
145
  }
146
 
 
147
  for base, (label, desc) in mappa.items():
148
  if preposizione.startswith(base):
 
 
 
149
  if base == "da" and any(c.dep_ == "aux:pass" for c in token.head.children):
150
+ return {"label": "Complemento d'Agente", "description": "Indica da chi è compiuta l'azione in una frase passiva."}
151
+ return {"label": label, "description": desc}
 
152
 
153
  return MAPPA_DEP.get("obl")
154
 
155
+ def get_full_phrase_for_token(token):
 
156
  """
157
+ FIXED: Costruisce un sintagma in modo preciso, raccogliendo solo i modificatori
158
+ strettamente collegati e gli elementi coordinati.
159
  """
160
+ phrase_tokens = []
161
 
162
+ # Funzione interna per raccogliere i token di un singolo elemento e i suoi figli diretti
163
+ def collect_children(t):
164
+ # Raccoglie i modificatori diretti (articoli, aggettivi, preposizioni)
165
+ children = [t]
166
+ for child in t.children:
167
+ if child.dep_ in ('det', 'amod', 'case', 'compound', 'advmod', 'appos'):
168
+ children.extend(collect_children(child)) # Raccoglie anche i figli dei figli (es. avverbi di aggettivi)
169
+ return children
170
+
171
+ # Raccoglie i token per il token principale
172
+ phrase_tokens.extend(collect_children(token))
173
+
174
+ # Gestisce la coordinazione (es. "libri e quaderni")
175
+ for child in token.children:
176
+ if child.dep_ == 'conj':
177
+ # Aggiunge la congiunzione (es. "e", "o")
178
+ cc = next((c for c in child.children if c.dep_ == 'cc'), None)
179
+ if cc:
180
+ phrase_tokens.append(cc)
181
+ # Aggiunge l'intero sintagma coordinato
182
+ phrase_tokens.extend(get_full_phrase_for_token(child))
183
+
184
+ # Ordina i token in base alla loro posizione originale e rimuove duplicati
185
+ unique_tokens = sorted(list(set(phrase_tokens)), key=lambda t: t.i)
186
+ text = " ".join(t.text for t in unique_tokens)
187
+ indices = {t.i for t in unique_tokens}
188
+ return text, indices
 
189
 
190
+ def costruisci_sintagmi_con_dettagli(tokens_proposizione):
191
+ """
192
+ FIXED: L'algoritmo ora processa ogni componente logico separatamente e con precisione.
193
+ """
194
  risultato_analisi = []
195
  indici_elaborati = set()
196
 
197
+ # Definisce le dipendenze che non sono "teste" di un sintagma ma parti di esso
198
+ DEPS_DA_SALTARE = {'det', 'amod', 'case', 'aux', 'aux:pass', 'cop', 'mark', 'cc', 'advmod', 'compound', 'appos'}
199
 
200
  for token in tokens_proposizione:
201
+ if token.i in indici_elaborati or token.dep_ in DEPS_DA_SALTARE:
202
  continue
 
 
 
 
203
 
204
+ testo_sintagma, indici_usati = get_full_phrase_for_token(token)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
  dep = token.dep_
207
+ if dep in ('obl', 'obl:agent', 'nmod'):
208
  info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
209
  else:
210
  info_etichetta = MAPPA_DEP.get(dep, {"label": dep.capitalize(), "description": "Relazione non mappata."})
211
 
212
+ # Caso speciale per predicato nominale
213
+ if dep == "ROOT" and any(c.dep_ == 'cop' for c in token.children):
214
+ info_etichetta = {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."}
215
+
216
  risultato_analisi.append({
217
  "text": testo_sintagma,
218
  "label_info": info_etichetta,
 
221
  "pos": f"{token.pos_}: {spiega_in_italiano(token.pos_)}",
222
  "tag": f"{token.tag_}: {spiega_in_italiano(token.tag_)}",
223
  "morph": traduci_morfologia(str(token.morph))
224
+ },
225
+ "token_index": token.i
226
  })
227
  indici_elaborati.update(indici_usati)
228
 
229
+ # Aggiungi componenti saltati (es. copula, congiunzioni) che sono importanti
230
+ for token in tokens_proposizione:
231
+ if token.i not in indici_elaborati and token.dep_ in ('cop', 'cc'):
232
+ risultato_analisi.append({
233
+ "text": token.text,
234
+ "label_info": MAPPA_DEP.get(token.dep_),
235
+ "token_details": { "lemma": token.lemma_, "pos": f"{token.pos_}: {spiega_in_italiano(token.pos_)}", "morph": traduci_morfologia(str(token.morph)) },
236
+ "token_index": token.i
237
+ })
238
+
239
+ # Ordina i risultati finali in base all'indice del token principale
240
+ risultato_analisi.sort(key=lambda x: x['token_index'])
241
  return risultato_analisi
242
 
243
+ def analizza_proposizione_con_dettagli(tokens):
244
+ tokens_validi = [t for t in tokens if not t.is_punct and not t.is_space]
245
+ return costruisci_sintagmi_con_dettagli(tokens_validi)
246
 
247
  # ------------------------------
248
  # Routes
249
  # ------------------------------
250
  @app.route("/")
251
  def home():
252
+ status = "ok" if nlp else "model_missing"
253
  return jsonify({
254
+ "messaggio": "API analisi logica in esecuzione", "modello_spacy": IT_MODEL or "Nessuno",
255
+ "model_status": status, "model_error": MODEL_LOAD_ERROR, "endpoint": "/api/analyze"
 
 
 
256
  })
257
 
258
  @app.route('/api/analyze', methods=['POST'])
259
  def analizza_frase():
260
+ if not nlp:
261
  return jsonify({"errore": "Modello spaCy non caricato.", "dettagli": MODEL_LOAD_ERROR}), 503
262
 
263
  try:
 
268
 
269
  doc = nlp(frase)
270
 
271
+ proposizioni_subordinate, indici_subordinate = [], set()
 
272
  SUBORD_DEPS = {"acl:relcl", "advcl", "ccomp", "csubj", "xcomp", "acl", "parataxis"}
273
 
274
  for token in doc:
275
+ if token.dep_ in SUBORD_DEPS and token.i not in indici_subordinate:
276
  subtree = list(token.subtree)
277
+ indici_subtree = {t.i for t in subtree}
278
+ indici_subordinate.update(indici_subtree)
279
+ info_tipo = MAPPA_DEP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Frase che dipende da un'altra."})
280
+ proposizioni_subordinate.append({
281
+ "type_info": info_tipo,
282
+ "text": " ".join(t.text for t in subtree if not t.is_punct).strip(),
283
+ "analysis": analizza_proposizione_con_dettagli(subtree)
284
+ })
 
285
 
286
  token_principale = [t for t in doc if t.i not in indici_subordinate]
287
 
 
289
  visti = set()
290
  for ent in doc.ents:
291
  if ent.text not in visti:
292
+ visti.add(ent.text)
293
  entita_nominate.append({
294
+ "text": ent.text, "label": ent.label_,
295
+ "explanation": f"{SPIEGAZIONI_ENT_IT.get(ent.label_, ent.label_)}"
 
296
  })
 
297
 
298
  analisi_finale = {
299
+ "full_sentence": frase, "model": IT_MODEL,
 
300
  "main_clause": {
301
+ "text": " ".join(t.text for t in token_principale if not t.is_punct).strip(),
302
  "analysis": analizza_proposizione_con_dettagli(token_principale)
303
  },
304
  "subordinate_clauses": proposizioni_subordinate,