devusman commited on
Commit
8153da1
·
verified ·
1 Parent(s): 3bd4373

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -63
app.py CHANGED
@@ -6,12 +6,13 @@ import traceback
6
 
7
  # --- SEZIONE CARICAMENTO MODELLO ---
8
  try:
9
- # Carica il modello italiano di spaCy
10
- nlp = spacy.load("it_core_news_sm")
11
  except OSError:
12
  raise RuntimeError(
13
- "Impossibile trovare il modello 'it_core_news_sm'. "
14
- "Assicurati che sia elencato e installato dal tuo file requirements.txt."
 
15
  )
16
  # --- FINE SEZIONE ---
17
 
@@ -35,36 +36,43 @@ SPIEGAZIONI_ENT_IT = {
35
  "MISC": "Miscellanea: Entità che non rientrano nelle altre categorie (es. eventi, nazionalità, prodotti)."
36
  }
37
 
38
- # NUOVA SEZIONE: Dizionario per le traduzioni morfologiche
39
  TRADUZIONI_MORFOLOGIA = {
40
  # Chiavi
41
  "Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo",
42
  "Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome",
43
  "Clitic": "Clitico", "Definite": "Definitezza", "Degree": "Grado",
 
 
44
  # Valori
45
  "Masc": "Maschile", "Fem": "Femminile",
46
  "Sing": "Singolare", "Plur": "Plurale",
47
  "Ind": "Indicativo", "Sub": "Congiuntivo", "Cnd": "Condizionale", "Imp": "Imperativo", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio",
48
- "Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Imp": "Imperfetto",
49
  "1": "1ª", "2": "2ª", "3": "3ª",
50
  "Fin": "Finita", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio",
51
- "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo", "Art": "Articolativo",
52
- "Yes": "Sì",
53
  "Ind": "Indeterminato", "Def": "Determinato",
54
- "Abs": "Assoluto", "Cmp": "Comparativo",
 
 
 
55
  }
56
 
57
  def spiega_in_italiano(tag, tipo='pos'):
58
  """Fornisce una spiegazione in italiano per un tag POS o di entità."""
59
  if tipo == 'pos':
60
- return SPIEGAZIONI_POS_IT.get(tag, tag)
 
 
61
  if tipo == 'ent':
62
  return SPIEGAZIONI_ENT_IT.get(tag, tag)
63
  return tag
64
 
65
  def traduci_morfologia(morph_str):
66
  """Traduce la stringa morfologica in un formato leggibile in italiano."""
67
- if not morph_str:
68
  return "Non disponibile"
69
 
70
  parti = morph_str.split('|')
@@ -76,15 +84,15 @@ def traduci_morfologia(morph_str):
76
  valore_tradotto = TRADUZIONI_MORFOLOGIA.get(valore, valore)
77
  parti_tradotte.append(f"{chiave_tradotta}: {valore_tradotto}")
78
  else:
79
- # Gestisce i casi in cui non c'è una coppia chiave=valore
80
  parti_tradotte.append(TRADUZIONI_MORFOLOGIA.get(parte, parte))
81
-
82
- return ", ".join(parti_tradotte)
83
  # --- FINE SEZIONE TRADUZIONI ---
84
 
85
 
86
  MAPPA_DEP = {
87
  "nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."},
 
88
  "ROOT": {"label": "Predicato Verbale", "description": "Esprime l'azione o lo stato del soggetto."},
89
  "obj": {"label": "Complemento Oggetto", "description": "Indica l'oggetto diretto dell'azione del verbo."},
90
  "iobj": {"label": "Complemento di Termine", "description": "Indica a chi o a cosa è destinata l'azione."},
@@ -96,59 +104,107 @@ MAPPA_DEP = {
96
  "acl:relcl": {"label": "Proposizione Subordinata Relativa", "description": "Frase che espande un nome, introdotta da un pronome relativo."},
97
  "advcl": {"label": "Proposizione Subordinata Avverbiale", "description": "Frase che funziona come un avverbio, modificando il verbo della principale."},
98
  "ccomp": {"label": "Proposizione Subordinata Oggettiva", "description": "Frase che funge da complemento oggetto del verbo della principale."},
99
- "csubj": {"label": "Proposizione Subordinata Soggettiva", "description": "Frase che funge da soggetto del verbo della principale."}
 
 
 
 
 
 
100
  }
101
 
102
  def ottieni_tipo_complemento_con_dettagli(token):
103
  preposizione = ""
 
104
  for figlio in token.children:
105
  if figlio.dep_ == "case":
106
  preposizione = figlio.text.lower()
107
  break
108
- if not preposizione and token.head.dep_ == 'obl':
109
  for figlio in token.head.children:
110
- if figlio.dep_ == "case":
111
  preposizione = figlio.text.lower()
112
  break
113
 
114
- if preposizione in ["di", "del", "dello", "della", "dei", "degli", "delle"]:
115
- return {"label": "Complemento di Specificazione", "description": "Risponde alla domanda 'di chi?', 'di che cosa?'."}
116
- if preposizione in ["a", "al", "allo", "alla", "ai", "agli", "alle"]:
117
- return {"label": "Complemento di Termine", "description": "Risponde alla domanda 'a chi?', 'a che cosa?'."}
118
- if preposizione in ["da", "dal", "dallo", "dalla", "dai", "dagli", "dalle"]:
119
- if any(figlio.dep_ == 'aux:pass' for figlio in token.head.children):
120
- return {"label": "Complemento d'Agente", "description": "Indica da chi è compiuta l'azione in una frase passiva."}
121
- return {"label": "Complemento di Moto da Luogo", "description": "Indica il luogo da cui inizia un movimento."}
122
- if preposizione in ["in", "nel", "nello", "nella", "nei", "negli", "nelle"]:
123
- return {"label": "Complemento di Stato in Luogo", "description": "Indica il luogo in cui si svolge un'azione o ci si trova."}
124
- if preposizione in ["con", "col", "coi"]:
125
- return {"label": "Complemento di Compagnia o Mezzo", "description": "Indica la persona/animale con cui si compie l'azione o lo strumento utilizzato."}
126
- if preposizione in ["su", "sul", "sullo", "sulla", "sui", "sugli", "sulle"]:
127
- return {"label": "Complemento di Argomento o Luogo", "description": "Indica l'argomento di cui si parla o il luogo su cui si trova qualcosa."}
128
- if preposizione in ["per"]:
129
- return {"label": "Complemento di Fine o Causa", "description": "Indica lo scopo o la causa di un'azione."}
130
- if preposizione in ["tra", "fra"]:
131
- return {"label": "Complemento di Luogo o Tempo (Partitivo)", "description": "Indica una posizione intermedia o una scelta all'interno di un gruppo."}
132
-
133
- return {"label": "Complemento Indiretto", "description": "Fornisce un'informazione generica non classificata in modo più specifico."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  def ottieni_testo_completo(token):
136
- token_sintagma = [token] + sorted([t for t in token.children if t.dep_ in ('det', 'amod', 'case', 'advmod')], key=lambda x: x.i)
137
- token_sintagma.sort(key=lambda x: x.i)
138
- return " ".join(t.text for t in token_sintagma)
 
 
 
 
 
 
 
 
139
 
140
  def costruisci_sintagmi_con_dettagli(lista_token):
141
  mappa_sintagmi = {}
142
 
143
  for token in lista_token:
144
- if token.dep_ not in ['det', 'case', 'amod', 'punct', 'aux', 'cop', 'mark']:
145
  mappa_sintagmi[token.i] = {
146
  "text": ottieni_testo_completo(token),
147
  "token_details": {
148
  "lemma": token.lemma_,
149
  "pos": f"{token.pos_}: {spiega_in_italiano(token.pos_, 'pos')}",
150
  "tag": f"{token.tag_}: {spiega_in_italiano(token.tag_, 'pos')}",
151
- "morph": traduci_morfologia(str(token.morph)) # AGGIORNATO
152
  },
153
  "label_info": {},
154
  "token": token
@@ -163,35 +219,39 @@ def costruisci_sintagmi_con_dettagli(lista_token):
163
 
164
  token = sintagma['token']
165
  dep = token.dep_
166
- info_etichetta = {}
167
 
168
  if dep == "ROOT":
169
- e_nominale = any(c.dep_ == 'cop' for c in token.children)
 
 
170
  if e_nominale:
171
- copula = [c for c in token.children if c.dep_ == 'cop'][0]
172
  nome_del_predicato = ottieni_testo_completo(token)
173
  risultato_analisi.append({
174
  "text": copula.text,
175
  "label_info": {"label": "Copula", "description": "Verbo 'essere' che collega il soggetto alla parte nominale."},
176
  "token_details": {
177
- "lemma": copula.lemma_,
178
- "pos": f"{copula.pos_}: {spiega_in_italiano(copula.pos_, 'pos')}",
179
- "tag": f"{copula.tag_}: {spiega_in_italiano(copula.tag_, 'pos')}",
180
- "morph": traduci_morfologia(str(copula.morph)) # AGGIORNATO
181
  }
182
  })
183
  risultato_analisi.append({
184
  "text": nome_del_predicato,
185
  "label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."},
186
- "token_details": sintagma["token_details"]
187
  })
 
 
188
  else:
189
  info_etichetta = MAPPA_DEP.get(dep, {})
190
- elif dep == 'obl':
191
  info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
192
- elif dep in MAPPA_DEP:
193
- info_etichetta = MAPPA_DEP[dep]
194
-
195
  if info_etichetta:
196
  sintagma_da_aggiungere = {
197
  "text": sintagma['text'],
@@ -203,10 +263,17 @@ def costruisci_sintagmi_con_dettagli(lista_token):
203
 
204
  indici_elaborati.add(indice)
205
 
206
- return risultato_analisi
 
 
 
 
 
 
 
207
 
208
  def analizza_proposizione_con_dettagli(token_proposizione):
209
- token_nella_proposizione = [t for t in token_proposizione if t.dep_ != 'mark']
210
  return costruisci_sintagmi_con_dettagli(token_nella_proposizione)
211
 
212
  @app.route("/")
@@ -220,14 +287,17 @@ def analizza_frase():
220
  if not dati or 'sentence' not in dati:
221
  return jsonify({"errore": "Frase non fornita"}), 400
222
 
223
- frase = dati['sentence']
 
 
 
224
  doc = nlp(frase)
225
 
226
  proposizioni_subordinate = []
227
  indici_subordinate = set()
228
 
229
  for token in doc:
230
- if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj"]:
231
  token_proposizione_subordinata = list(token.subtree)
232
  for t in token_proposizione_subordinata:
233
  indici_subordinate.add(t.i)
@@ -239,7 +309,7 @@ def analizza_frase():
239
 
240
  proposizioni_subordinate.append({
241
  "type_info": info_tipo_subordinata,
242
- "text": " ".join(t.text for t in token_proposizione_subordinata if not t.is_punct),
243
  "intro": intro,
244
  "analysis": analizza_proposizione_con_dettagli(token_proposizione_subordinata)
245
  })
@@ -252,14 +322,22 @@ def analizza_frase():
252
  "explanation": spiega_in_italiano(ent.label_, 'ent')
253
  } for ent in doc.ents]
254
 
 
 
 
 
 
 
 
 
255
  analisi_finale = {
256
  "full_sentence": frase,
257
  "main_clause": {
258
- "text": " ".join(t.text for t in token_proposizione_principale if not t.is_punct),
259
  "analysis": analizza_proposizione_con_dettagli(token_proposizione_principale)
260
  },
261
  "subordinate_clauses": proposizioni_subordinate,
262
- "named_entities": entita_nominate
263
  }
264
 
265
  return jsonify(analisi_finale)
@@ -267,8 +345,8 @@ def analizza_frase():
267
  except Exception as e:
268
  print(f"Errore durante l'analisi: {e}")
269
  traceback.print_exc()
270
- return jsonify({"errore": "Si è verificato un errore interno."}), 500
271
 
272
  if __name__ == '__main__':
273
  porta = int(os.environ.get("PORT", 8080))
274
- app.run(host="0.0.0.0", port=porta, debug=True)
 
6
 
7
  # --- SEZIONE CARICAMENTO MODELLO ---
8
  try:
9
+ # Carica un modello più accurato per l'italiano di spaCy (usa 'lg' per maggiore precisione)
10
+ nlp = spacy.load("it_core_news_lg")
11
  except OSError:
12
  raise RuntimeError(
13
+ "Impossibile trovare il modello 'it_core_news_lg'. "
14
+ "Assicurati che sia elencato e installato dal tuo file requirements.txt. "
15
+ "Puoi scaricarlo con: python -m spacy download it_core_news_lg"
16
  )
17
  # --- FINE SEZIONE ---
18
 
 
36
  "MISC": "Miscellanea: Entità che non rientrano nelle altre categorie (es. eventi, nazionalità, prodotti)."
37
  }
38
 
39
+ # Dizionario per le traduzioni morfologiche (espanso per coprire più casi)
40
  TRADUZIONI_MORFOLOGIA = {
41
  # Chiavi
42
  "Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo",
43
  "Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome",
44
  "Clitic": "Clitico", "Definite": "Definitezza", "Degree": "Grado",
45
+ "Case": "Caso", "Poss": "Possessivo", "Reflex": "Riflessivo",
46
+ "Aspect": "Aspetto", "Voice": "Voce",
47
  # Valori
48
  "Masc": "Maschile", "Fem": "Femminile",
49
  "Sing": "Singolare", "Plur": "Plurale",
50
  "Ind": "Indicativo", "Sub": "Congiuntivo", "Cnd": "Condizionale", "Imp": "Imperativo", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio",
51
+ "Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Imp": "Imperfetto", "Pqp": "Trapassato",
52
  "1": "1ª", "2": "2ª", "3": "3ª",
53
  "Fin": "Finita", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio",
54
+ "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo", "Art": "Articolativo", "Ind": "Indefinito",
55
+ "Yes": "Sì", "No": "No",
56
  "Ind": "Indeterminato", "Def": "Determinato",
57
+ "Abs": "Assoluto", "Cmp": "Comparativo", "Sup": "Superlativo",
58
+ "Nom": "Nominativo", "Acc": "Accusativo", "Gen": "Genitivo", "Dat": "Dativo",
59
+ "Perf": "Perfetto", "Prog": "Progressivo",
60
+ "Act": "Attiva", "Pass": "Passiva",
61
  }
62
 
63
  def spiega_in_italiano(tag, tipo='pos'):
64
  """Fornisce una spiegazione in italiano per un tag POS o di entità."""
65
  if tipo == 'pos':
66
+ # Per i tag dettagliati, estrai la parte POS principale se necessario
67
+ pos_base = tag.split("__")[0] if "__" in tag else tag
68
+ return SPIEGAZIONI_POS_IT.get(pos_base, tag)
69
  if tipo == 'ent':
70
  return SPIEGAZIONI_ENT_IT.get(tag, tag)
71
  return tag
72
 
73
  def traduci_morfologia(morph_str):
74
  """Traduce la stringa morfologica in un formato leggibile in italiano."""
75
+ if not morph_str or morph_str == "___":
76
  return "Non disponibile"
77
 
78
  parti = morph_str.split('|')
 
84
  valore_tradotto = TRADUZIONI_MORFOLOGIA.get(valore, valore)
85
  parti_tradotte.append(f"{chiave_tradotta}: {valore_tradotto}")
86
  else:
 
87
  parti_tradotte.append(TRADUZIONI_MORFOLOGIA.get(parte, parte))
88
+
89
+ return ", ".join(sorted(set(parti_tradotte))) or "Non disponibile" # Sort and unique for cleanliness
90
  # --- FINE SEZIONE TRADUZIONI ---
91
 
92
 
93
  MAPPA_DEP = {
94
  "nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."},
95
+ "nsubj:pass": {"label": "Soggetto (Passivo)", "description": "Soggetto in una costruzione passiva."},
96
  "ROOT": {"label": "Predicato Verbale", "description": "Esprime l'azione o lo stato del soggetto."},
97
  "obj": {"label": "Complemento Oggetto", "description": "Indica l'oggetto diretto dell'azione del verbo."},
98
  "iobj": {"label": "Complemento di Termine", "description": "Indica a chi o a cosa è destinata l'azione."},
 
104
  "acl:relcl": {"label": "Proposizione Subordinata Relativa", "description": "Frase che espande un nome, introdotta da un pronome relativo."},
105
  "advcl": {"label": "Proposizione Subordinata Avverbiale", "description": "Frase che funziona come un avverbio, modificando il verbo della principale."},
106
  "ccomp": {"label": "Proposizione Subordinata Oggettiva", "description": "Frase che funge da complemento oggetto del verbo della principale."},
107
+ "csubj": {"label": "Proposizione Subordinata Soggettiva", "description": "Frase che funge da soggetto del verbo della principale."},
108
+ "xcomp": {"label": "Complemento Predicativo", "description": "Complemento che completa il significato del verbo."},
109
+ "acl": {"label": "Modificatore Relativo", "description": "Clausola che modifica un nome."},
110
+ "compound": {"label": "Composto", "description": "Parte di un composto nominale."},
111
+ "flat": {"label": "Nome Piatto", "description": "Parte di un nome proprio o espressione fissa."},
112
+ "conj": {"label": "Congiunzione Coordinata", "description": "Elemento coordinato con un altro."},
113
+ "cc": {"label": "Congiunzione Coordinante", "description": "Congiunzione che collega elementi coordinati."}
114
  }
115
 
116
  def ottieni_tipo_complemento_con_dettagli(token):
117
  preposizione = ""
118
+ # Cerca la preposizione nei figli o nel contesto
119
  for figlio in token.children:
120
  if figlio.dep_ == "case":
121
  preposizione = figlio.text.lower()
122
  break
123
+ if not preposizione and token.dep_ == 'obl':
124
  for figlio in token.head.children:
125
+ if figlio.dep_ == "case" and figlio.head == token:
126
  preposizione = figlio.text.lower()
127
  break
128
 
129
+ # Mappa preposizioni a complementi (espansa per più varianti e casi)
130
+ mappa_preposizioni = {
131
+ "di": "Complemento di Specificazione",
132
+ "del": "Complemento di Specificazione",
133
+ "dello": "Complemento di Specificazione",
134
+ "della": "Complemento di Specificazione",
135
+ "dei": "Complemento di Specificazione",
136
+ "degli": "Complemento di Specificazione",
137
+ "delle": "Complemento di Specificazione",
138
+ "a": "Complemento di Termine",
139
+ "al": "Complemento di Termine",
140
+ "allo": "Complemento di Termine",
141
+ "alla": "Complemento di Termine",
142
+ "ai": "Complemento di Termine",
143
+ "agli": "Complemento di Termine",
144
+ "alle": "Complemento di Termine",
145
+ "da": "Complemento di Moto da Luogo",
146
+ "dal": "Complemento di Moto da Luogo",
147
+ "dallo": "Complemento di Moto da Luogo",
148
+ "dalla": "Complemento di Moto da Luogo",
149
+ "dai": "Complemento di Moto da Luogo",
150
+ "dagli": "Complemento di Moto da Luogo",
151
+ "dalle": "Complemento di Moto da Luogo",
152
+ "in": "Complemento di Stato in Luogo",
153
+ "nel": "Complemento di Stato in Luogo",
154
+ "nello": "Complemento di Stato in Luogo",
155
+ "nella": "Complemento di Stato in Luogo",
156
+ "nei": "Complemento di Stato in Luogo",
157
+ "negli": "Complemento di Stato in Luogo",
158
+ "nelle": "Complemento di Stato in Luogo",
159
+ "con": "Complemento di Compagnia o Mezzo",
160
+ "col": "Complemento di Compagnia o Mezzo",
161
+ "coi": "Complemento di Compagnia o Mezzo",
162
+ "su": "Complemento di Argomento o Luogo",
163
+ "sul": "Complemento di Argomento o Luogo",
164
+ "sullo": "Complemento di Argomento o Luogo",
165
+ "sulla": "Complemento di Argomento o Luogo",
166
+ "sui": "Complemento di Argomento o Luogo",
167
+ "sugli": "Complemento di Argomento o Luogo",
168
+ "sulle": "Complemento di Argomento o Luogo",
169
+ "per": "Complemento di Fine o Causa",
170
+ "tra": "Complemento di Luogo o Tempo (Partitivo)",
171
+ "fra": "Complemento di Luogo o Tempo (Partitivo)",
172
+ }
173
+
174
+ label = mappa_preposizioni.get(preposizione, "Complemento Indiretto")
175
+ description = "Fornisce un'informazione generica non classificata in modo più specifico." if label == "Complemento Indiretto" else "Risponde alla domanda appropriata per il tipo di complemento."
176
+
177
+ if preposizione.startswith("da") and any(figlio.dep_ == 'aux:pass' for figlio in token.head.children):
178
+ label = "Complemento d'Agente"
179
+ description = "Indica da chi è compiuta l'azione in una frase passiva."
180
+
181
+ return {"label": label, "description": description}
182
 
183
  def ottieni_testo_completo(token):
184
+ # Raccogli il sintagma completo ricorsivamente per includere modificatori nidificati
185
+ def raccogli_figli(t):
186
+ figli = list(t.children)
187
+ for f in figli:
188
+ if f.dep_ in ('det', 'amod', 'case', 'advmod', 'nmod', 'appos', 'acl', 'compound', 'flat'):
189
+ figli.extend(raccogli_figli(f))
190
+ return figli
191
+
192
+ token_sintagma = [token] + raccogli_figli(token)
193
+ token_sintagma = sorted(set(token_sintagma), key=lambda x: x.i) # Ordina e rimuovi duplicati
194
+ return " ".join(t.text for t in token_sintagma if not t.is_punct).strip()
195
 
196
  def costruisci_sintagmi_con_dettagli(lista_token):
197
  mappa_sintagmi = {}
198
 
199
  for token in lista_token:
200
+ if token.dep_ not in ['det', 'case', 'amod', 'punct', 'aux', 'cop', 'mark', 'cc', 'advmod', 'aux:pass']: # Espanso per skipping più elementi ausiliari
201
  mappa_sintagmi[token.i] = {
202
  "text": ottieni_testo_completo(token),
203
  "token_details": {
204
  "lemma": token.lemma_,
205
  "pos": f"{token.pos_}: {spiega_in_italiano(token.pos_, 'pos')}",
206
  "tag": f"{token.tag_}: {spiega_in_italiano(token.tag_, 'pos')}",
207
+ "morph": traduci_morfologia(str(token.morph))
208
  },
209
  "label_info": {},
210
  "token": token
 
219
 
220
  token = sintagma['token']
221
  dep = token.dep_
222
+ info_etichetta = MAPPA_DEP.get(dep, {"label": dep, "description": "Relazione non mappata."})
223
 
224
  if dep == "ROOT":
225
+ # Gestione predicato nominale migliorata
226
+ copula_children = [c for c in token.children if c.dep_ == 'cop']
227
+ e_nominale = bool(copula_children)
228
  if e_nominale:
229
+ copula = copula_children[0]
230
  nome_del_predicato = ottieni_testo_completo(token)
231
  risultato_analisi.append({
232
  "text": copula.text,
233
  "label_info": {"label": "Copula", "description": "Verbo 'essere' che collega il soggetto alla parte nominale."},
234
  "token_details": {
235
+ "lemma": copula.lemma_,
236
+ "pos": f"{copula.pos_}: {spiega_in_italiano(copula.pos_, 'pos')}",
237
+ "tag": f"{copula.tag_}: {spiega_in_italiano(copula.tag_, 'pos')}",
238
+ "morph": traduci_morfologia(str(copula.morph))
239
  }
240
  })
241
  risultato_analisi.append({
242
  "text": nome_del_predicato,
243
  "label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."},
244
+ "token_details": sintagma["token_details"]
245
  })
246
+ indici_elaborati.add(indice)
247
+ continue
248
  else:
249
  info_etichetta = MAPPA_DEP.get(dep, {})
250
+ elif dep in ('obl', 'obl:agent'):
251
  info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
252
+ elif dep == 'nsubj:pass':
253
+ info_etichetta = MAPPA_DEP.get('nsubj:pass', MAPPA_DEP['nsubj'])
254
+
255
  if info_etichetta:
256
  sintagma_da_aggiungere = {
257
  "text": sintagma['text'],
 
263
 
264
  indici_elaborati.add(indice)
265
 
266
+ # Rimuovi duplicati dal risultato finale
267
+ risultato_unico = []
268
+ testi_visti = set()
269
+ for item in risultato_analisi:
270
+ if item['text'] not in testi_visti:
271
+ risultato_unico.append(item)
272
+ testi_visti.add(item['text'])
273
+ return risultato_unico
274
 
275
  def analizza_proposizione_con_dettagli(token_proposizione):
276
+ token_nella_proposizione = [t for t in token_proposizione if t.dep_ != 'mark' and not t.is_punct and not t.is_space]
277
  return costruisci_sintagmi_con_dettagli(token_nella_proposizione)
278
 
279
  @app.route("/")
 
287
  if not dati or 'sentence' not in dati:
288
  return jsonify({"errore": "Frase non fornita"}), 400
289
 
290
+ frase = dati['sentence'].strip()
291
+ if not frase:
292
+ return jsonify({"errore": "Frase vuota"}), 400
293
+
294
  doc = nlp(frase)
295
 
296
  proposizioni_subordinate = []
297
  indici_subordinate = set()
298
 
299
  for token in doc:
300
+ if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj", "xcomp", "acl", "parataxis"]: # Espanso per più tipi, inclusa parataxis per coordinate
301
  token_proposizione_subordinata = list(token.subtree)
302
  for t in token_proposizione_subordinata:
303
  indici_subordinate.add(t.i)
 
309
 
310
  proposizioni_subordinate.append({
311
  "type_info": info_tipo_subordinata,
312
+ "text": " ".join(t.text for t in token_proposizione_subordinata if not t.is_punct and not t.is_space).strip(),
313
  "intro": intro,
314
  "analysis": analizza_proposizione_con_dettagli(token_proposizione_subordinata)
315
  })
 
322
  "explanation": spiega_in_italiano(ent.label_, 'ent')
323
  } for ent in doc.ents]
324
 
325
+ # Rimuovi duplicati dalle entità
326
+ entita_unica = []
327
+ testi_ent_visti = set()
328
+ for ent in entita_nominate:
329
+ if ent['text'] not in testi_ent_visti:
330
+ entita_unica.append(ent)
331
+ testi_ent_visti.add(ent['text'])
332
+
333
  analisi_finale = {
334
  "full_sentence": frase,
335
  "main_clause": {
336
+ "text": " ".join(t.text for t in token_proposizione_principale if not t.is_punct and not t.is_space).strip(),
337
  "analysis": analizza_proposizione_con_dettagli(token_proposizione_principale)
338
  },
339
  "subordinate_clauses": proposizioni_subordinate,
340
+ "named_entities": entita_unica
341
  }
342
 
343
  return jsonify(analisi_finale)
 
345
  except Exception as e:
346
  print(f"Errore durante l'analisi: {e}")
347
  traceback.print_exc()
348
+ return jsonify({"errore": "Si è verificato un errore interno.", "dettagli": str(e)}), 500
349
 
350
  if __name__ == '__main__':
351
  porta = int(os.environ.get("PORT", 8080))
352
+ app.run(host="0.0.0.0", port=porta, debug=False, threaded=True) # Threaded per gestire più richieste