devusman commited on
Commit
699c193
·
verified ·
1 Parent(s): 47669ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +183 -150
app.py CHANGED
@@ -4,23 +4,21 @@ from flask_cors import CORS
4
  import spacy
5
  import traceback
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
 
19
- # Inizializza l'app Flask
20
  app = Flask(__name__)
21
  CORS(app)
22
 
23
- # --- INIZIO SEZIONE TRADUZIONI ---
24
  SPIEGAZIONI_POS_IT = {
25
  "ADJ": "Aggettivo", "ADP": "Preposizione", "ADV": "Avverbio", "AUX": "Ausiliare",
26
  "CONJ": "Congiunzione", "CCONJ": "Congiunzione Coordinante", "SCONJ": "Congiunzione Subordinante",
@@ -36,60 +34,26 @@ SPIEGAZIONI_ENT_IT = {
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('|')
79
- parti_tradotte = []
80
- for parte in parti:
81
- if '=' in parte:
82
- chiave, valore = parte.split('=', 1)
83
- chiave_tradotta = TRADUZIONI_MORFOLOGIA.get(chiave, chiave)
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."},
@@ -110,12 +74,81 @@ MAPPA_DEP = {
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()
@@ -126,144 +159,144 @@ def ottieni_tipo_complemento_con_dettagli(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
211
- }
212
-
 
 
 
 
213
  risultato_analisi = []
214
  indici_elaborati = set()
215
-
216
  for indice, sintagma in sorted(mappa_sintagmi.items()):
217
  if indice in indici_elaborati:
218
  continue
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'],
258
- "label_info": info_etichetta
259
- }
260
- if sintagma.get("token_details"):
261
- sintagma_da_aggiungere["token_details"] = sintagma["token_details"]
262
- risultato_analisi.append(sintagma_da_aggiungere)
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:
@@ -273,9 +306,11 @@ def costruisci_sintagmi_con_dettagli(lista_token):
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("/")
280
  def home():
281
  return jsonify({"messaggio": "L'API per l'analisi logica è in esecuzione. Usa l'endpoint /api/analyze."})
@@ -292,18 +327,17 @@ def analizza_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)
304
 
305
  info_tipo_subordinata = MAPPA_DEP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Una frase che dipende da un'altra."})
306
-
307
  marcatore = [figlio for figlio in token.children if figlio.dep_ == 'mark']
308
  intro = marcatore[0].text if marcatore else ""
309
 
@@ -339,14 +373,13 @@ def analizza_frase():
339
  "subordinate_clauses": proposizioni_subordinate,
340
  "named_entities": entita_unica
341
  }
342
-
343
  return jsonify(analisi_finale)
344
 
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
 
4
  import spacy
5
  import traceback
6
 
7
+ # --- CARICAMENTO MODELLO ---
8
  try:
 
9
  nlp = spacy.load("it_core_news_lg")
10
  except OSError:
11
  raise RuntimeError(
12
  "Impossibile trovare il modello 'it_core_news_lg'. "
13
+ "Assicurati che sia elencato e installato nel tuo requirements.txt.\n"
14
+ "Comando per installarlo localmente: python -m spacy download it_core_news_lg"
15
  )
 
16
 
17
+ # --- INIZIALIZZAZIONE APP ---
18
  app = Flask(__name__)
19
  CORS(app)
20
 
21
+ # --- MAPPE DI TRADUZIONE / SPIEGAZIONI ---
22
  SPIEGAZIONI_POS_IT = {
23
  "ADJ": "Aggettivo", "ADP": "Preposizione", "ADV": "Avverbio", "AUX": "Ausiliare",
24
  "CONJ": "Congiunzione", "CCONJ": "Congiunzione Coordinante", "SCONJ": "Congiunzione Subordinante",
 
34
  "MISC": "Miscellanea: Entità che non rientrano nelle altre categorie (es. eventi, nazionalità, prodotti)."
35
  }
36
 
 
37
  TRADUZIONI_MORFOLOGIA = {
38
+ # chiavi
39
  "Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo",
40
  "Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome",
41
+ "Clitic": "Clitico", "Definite": "Definizione", "Degree": "Grado",
42
  "Case": "Caso", "Poss": "Possessivo", "Reflex": "Riflessivo",
43
+ "Aspect": "Aspetto", "Voice": "Voce", "Polarity": "Polarità",
44
+ # valori
45
+ "Masc": "Maschile", "Fem": "Femminile", "Sing": "Singolare", "Plur": "Plurale",
46
+ "Ind": "Indicativo", "Sub": "Congiuntivo", "Cnd": "Condizionale", "Imp": "Imperativo",
47
+ "Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Pqp": "Trapassato",
 
 
48
  "Fin": "Finita", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio",
49
+ "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo",
50
+ "Art": "Articolativo", "Indf": "Indeterminato", "Yes": "Sì", "No": "No",
 
51
  "Abs": "Assoluto", "Cmp": "Comparativo", "Sup": "Superlativo",
52
  "Nom": "Nominativo", "Acc": "Accusativo", "Gen": "Genitivo", "Dat": "Dativo",
53
  "Perf": "Perfetto", "Prog": "Progressivo",
54
  "Act": "Attiva", "Pass": "Passiva",
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  MAPPA_DEP = {
58
  "nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."},
59
  "nsubj:pass": {"label": "Soggetto (Passivo)", "description": "Soggetto in una costruzione passiva."},
 
74
  "compound": {"label": "Composto", "description": "Parte di un composto nominale."},
75
  "flat": {"label": "Nome Piatto", "description": "Parte di un nome proprio o espressione fissa."},
76
  "conj": {"label": "Congiunzione Coordinata", "description": "Elemento coordinato con un altro."},
77
+ "cc": {"label": "Congiunzione Coordinante", "description": "Congiunzione che collega elementi coordinati."},
78
+ "parataxis": {"label": "Paratassi", "description": "Frasi coordinate senza connettore esplicito."}
79
  }
80
 
81
+ # --- FUNZIONI DI UTILITÀ ---
82
+
83
+ def spiega_in_italiano(tag, tipo='pos'):
84
+ """
85
+ Fornisce spiegazione per POS o entità.
86
+ - tipo == 'pos' => tag dovrebbe essere token.pos_ (es. VERB, NOUN)
87
+ - tipo == 'ent' => ent label (PER, LOC, ORG...)
88
+ """
89
+ if tipo == 'pos':
90
+ key = tag.upper().split(":")[0] # normalizza caso e rimuove eventuali dettagli
91
+ return SPIEGAZIONI_POS_IT.get(key, key)
92
+ if tipo == 'ent':
93
+ return SPIEGAZIONI_ENT_IT.get(tag, tag)
94
+ return tag
95
+
96
+ def traduci_morfologia_from_token(token):
97
+ """
98
+ Usa token.morph (meglio token.morph.to_dict()) per costruire una descrizione leggibile.
99
+ """
100
+ try:
101
+ morph_dict = token.morph.to_dict()
102
+ except Exception:
103
+ # Fallback alla stringa originale
104
+ morph_str = str(token.morph)
105
+ if not morph_str:
106
+ return "Non disponibile"
107
+ parti = morph_str.split("|")
108
+ mappate = []
109
+ for parte in parti:
110
+ if "=" in parte:
111
+ k, v = parte.split("=", 1)
112
+ mappate.append(f"{TRADUZIONI_MORFOLOGIA.get(k,k)}: {TRADUZIONI_MORFOLOGIA.get(v, v)}")
113
+ else:
114
+ mappate.append(TRADUZIONI_MORFOLOGIA.get(parte, parte))
115
+ return ", ".join(mappate) if mappate else "Non disponibile"
116
+
117
+ parti_tradotte = []
118
+ for k, v in sorted(morph_dict.items()):
119
+ k_tr = TRADUZIONI_MORFOLOGIA.get(k, k)
120
+ if isinstance(v, (list, tuple)):
121
+ v_str = ", ".join(TRADUZIONI_MORFOLOGIA.get(x, x) for x in v)
122
+ else:
123
+ v_str = TRADUZIONI_MORFOLOGIA.get(v, v)
124
+ parti_tradotte.append(f"{k_tr}: {v_str}")
125
+ return ", ".join(parti_tradotte) if parti_tradotte else "Non disponibile"
126
+
127
+ def get_verb_phrase(token):
128
+ """
129
+ Costruisce la 'verb phrase' completa: aggiunge ausiliari, negazioni, particelle legate.
130
+ Ordina i token per indice per mantenere la sequenza corretta.
131
+ """
132
+ verb_related = []
133
+ # includi token stesso se è verbo o copula
134
+ if token.pos_ in ("VERB", "AUX") or token.dep_ in ("cop", "ROOT"):
135
+ verb_related.append(token)
136
+ # cerca ausiliari, negazioni e copula fra i figli e nella testa (se la testa è verbo)
137
+ for t in list(token.children):
138
+ if t.dep_ in ('aux', 'aux:pass', 'neg', 'cop', 'prt'):
139
+ verb_related.append(t)
140
+ # talvolta l'ausiliare è head (in casi particolari); includi ausiliari nella testa che hanno head==token or viceversa
141
+ # includi anche eventuali elementi nella subtree stretta che sono parte del verbo
142
+ for t in token.subtree:
143
+ if t.dep_ in ('aux', 'aux:pass', 'neg', 'cop', 'prt') and t not in verb_related:
144
+ verb_related.append(t)
145
+
146
+ # rimuovi duplicati e ordina
147
+ verb_related = sorted(set(verb_related), key=lambda x: x.i)
148
+ return " ".join(t.text for t in verb_related).strip() or token.text
149
+
150
  def ottieni_tipo_complemento_con_dettagli(token):
151
  preposizione = ""
 
152
  for figlio in token.children:
153
  if figlio.dep_ == "case":
154
  preposizione = figlio.text.lower()
 
159
  preposizione = figlio.text.lower()
160
  break
161
 
 
162
  mappa_preposizioni = {
163
  "di": "Complemento di Specificazione",
164
+ "del": "Complemento di Specificazione", "dello": "Complemento di Specificazione",
165
+ "della": "Complemento di Specificazione", "dei": "Complemento di Specificazione",
166
+ "a": "Complemento di Termine", "al": "Complemento di Termine",
167
+ "da": "Complemento di Moto da Luogo", "dal": "Complemento di Moto da Luogo",
168
+ "in": "Complemento di Stato in Luogo", "nel": "Complemento di Stato in Luogo",
169
+ "con": "Complemento di Compagnia o Mezzo", "su": "Complemento di Argomento o Luogo",
170
+ "per": "Complemento di Fine o Causa", "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_ in ('aux:pass','aux') 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
+ """
185
+ Raccoglie il sintagma completo includendo determinanti, aggettivi, apposizioni, complementi stretti.
186
+ """
187
  def raccogli_figli(t):
188
+ figli = []
189
+ for f in t.children:
190
+ if f.dep_ in ('det', 'amod', 'case', 'advmod', 'nmod', 'appos', 'acl', 'compound', 'flat', 'nummod'):
191
+ figli.append(f)
192
  figli.extend(raccogli_figli(f))
193
  return figli
194
 
195
  token_sintagma = [token] + raccogli_figli(token)
196
+ token_sintagma = sorted(set(token_sintagma), key=lambda x: x.i)
197
  return " ".join(t.text for t in token_sintagma if not t.is_punct).strip()
198
 
199
  def costruisci_sintagmi_con_dettagli(lista_token):
200
+ """
201
+ Costruisce la lista di sintagmi (soggetto, predicato, complementi) con dettagli per ogni token significativo.
202
+ Non scartiamo ausiliari e copula: li rappresentiamo opportunamente.
203
+ """
204
  mappa_sintagmi = {}
205
+
206
+ # selezioniamo token utili (scartiamo solo punteggiatura e spazi)
207
  for token in lista_token:
208
+ if token.is_punct or token.is_space:
209
+ continue
210
+ mappa_sintagmi[token.i] = {
211
+ "text": ottieni_testo_completo(token),
212
+ "token_details": {
213
+ "text": token.text,
214
+ "lemma": token.lemma_,
215
+ "pos": token.pos_,
216
+ "pos_explanation": spiega_in_italiano(token.pos_, 'pos'),
217
+ "tag": token.tag_,
218
+ "morph_raw": str(token.morph),
219
+ "morph": traduci_morfologia_from_token(token)
220
+ },
221
+ "label_info": {},
222
+ "token": token
223
+ }
224
+
225
  risultato_analisi = []
226
  indici_elaborati = set()
227
+
228
  for indice, sintagma in sorted(mappa_sintagmi.items()):
229
  if indice in indici_elaborati:
230
  continue
231
+
232
  token = sintagma['token']
233
  dep = token.dep_
234
  info_etichetta = MAPPA_DEP.get(dep, {"label": dep, "description": "Relazione non mappata."})
235
 
236
+ # gestione ROOT e predicato nominale con copula
237
  if dep == "ROOT":
238
+ # trova eventuali copule collegate (cop o aux:cop)
239
+ copula_children = [c for c in token.children if c.dep_ in ('cop', 'aux:cop')]
240
+ if copula_children:
241
+ cop = copula_children[0]
242
+ # copula come verbo
 
243
  risultato_analisi.append({
244
+ "text": cop.text,
245
+ "label_info": {"label": "Copula", "description": "Verbo 'essere' che collega soggetto e parte nominale."},
246
  "token_details": {
247
+ "lemma": cop.lemma_,
248
+ "pos": cop.pos_,
249
+ "pos_explanation": spiega_in_italiano(cop.pos_, 'pos'),
250
+ "tag": cop.tag_,
251
+ "morph": traduci_morfologia_from_token(cop),
252
+ "verb_phrase": get_verb_phrase(cop)
253
  }
254
  })
255
+ # parte nominale (il nome/aggettivo che segue)
256
  risultato_analisi.append({
257
+ "text": sintagma["text"],
258
+ "label_info": {"label": "Parte Nominale del Predicato", "description": "Parte nominale che descrive il soggetto."},
259
  "token_details": sintagma["token_details"]
260
  })
261
  indici_elaborati.add(indice)
262
  continue
263
  else:
264
+ # ROOT come verbo principale: mostriamo verb phrase completa
265
+ sintagma_da_aggiungere = {
266
+ "text": sintagma['text'],
267
+ "label_info": info_etichetta,
268
+ "token_details": dict(sintagma['token_details'])
269
+ }
270
+ # se è verbo, aggiungi la verb_phrase
271
+ if token.pos_ in ("VERB", "AUX"):
272
+ sintagma_da_aggiungere["token_details"]["verb_phrase"] = get_verb_phrase(token)
273
+ sintagma_da_aggiungere["token_details"]["verb_morph"] = traduci_morfologia_from_token(token)
274
+ risultato_analisi.append(sintagma_da_aggiungere)
275
+ indici_elaborati.add(indice)
276
+ continue
277
+
278
+ # gestione complementi obl e agent
279
+ if dep in ('obl', 'obl:agent', 'obl:mod'):
280
  info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
 
 
281
 
282
+ if dep == 'nsubj:pass':
283
+ info_etichetta = MAPPA_DEP.get('nsubj:pass', MAPPA_DEP['nsubj'])
 
 
 
 
 
 
284
 
285
+ sintagma_da_aggiungere = {
286
+ "text": sintagma['text'],
287
+ "label_info": info_etichetta
288
+ }
289
+ if sintagma.get("token_details"):
290
+ # se è un verbo o ausiliare aggiungiamo la verb_phrase
291
+ if token.pos_ in ("VERB", "AUX"):
292
+ sintagma['token_details']["verb_phrase"] = get_verb_phrase(token)
293
+ sintagma['token_details']["verb_morph"] = traduci_morfologia_from_token(token)
294
+ sintagma_da_aggiungere["token_details"] = sintagma["token_details"]
295
+
296
+ risultato_analisi.append(sintagma_da_aggiungere)
297
  indici_elaborati.add(indice)
298
+
299
+ # rimuovi duplicati (testo)
300
  risultato_unico = []
301
  testi_visti = set()
302
  for item in risultato_analisi:
 
306
  return risultato_unico
307
 
308
  def analizza_proposizione_con_dettagli(token_proposizione):
309
+ token_nella_proposizione = [t for t in token_proposizione if not t.is_punct and not t.is_space]
310
  return costruisci_sintagmi_con_dettagli(token_nella_proposizione)
311
 
312
+ # --- ENDPOINT ---
313
+
314
  @app.route("/")
315
  def home():
316
  return jsonify({"messaggio": "L'API per l'analisi logica è in esecuzione. Usa l'endpoint /api/analyze."})
 
327
  return jsonify({"errore": "Frase vuota"}), 400
328
 
329
  doc = nlp(frase)
330
+
331
  proposizioni_subordinate = []
332
  indici_subordinate = set()
333
+
334
  for token in doc:
335
+ if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj", "xcomp", "acl", "parataxis", "parataxis:rel"]:
336
  token_proposizione_subordinata = list(token.subtree)
337
  for t in token_proposizione_subordinata:
338
  indici_subordinate.add(t.i)
339
 
340
  info_tipo_subordinata = MAPPA_DEP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Una frase che dipende da un'altra."})
 
341
  marcatore = [figlio for figlio in token.children if figlio.dep_ == 'mark']
342
  intro = marcatore[0].text if marcatore else ""
343
 
 
373
  "subordinate_clauses": proposizioni_subordinate,
374
  "named_entities": entita_unica
375
  }
376
+
377
  return jsonify(analisi_finale)
378
 
379
  except Exception as e:
 
380
  traceback.print_exc()
381
  return jsonify({"errore": "Si è verificato un errore interno.", "dettagli": str(e)}), 500
382
 
383
  if __name__ == '__main__':
384
  porta = int(os.environ.get("PORT", 8080))
385
+ app.run(host="0.0.0.0", port=porta, debug=False, threaded=True)