devusman commited on
Commit
96e4672
·
1 Parent(s): 31edf0b
Files changed (1) hide show
  1. app.py +129 -129
app.py CHANGED
@@ -4,25 +4,25 @@ from flask_cors import CORS
4
  import spacy
5
  import traceback
6
 
7
- # --- CORRECTED MODEL LOADING SECTION ---
8
  try:
9
- # Laad het Italiaanse model van spaCy
10
  nlp = spacy.load("it_core_news_sm")
11
  except OSError:
12
  raise RuntimeError(
13
- "Could not find the 'it_core_news_sm' model. "
14
- "Please ensure it is listed and installed from your requirements.txt file."
15
  )
16
- # --- END SECTION ---
17
 
18
- # Initialiseer de Flask-app
19
  app = Flask(__name__)
20
 
21
- # Schakel Cross-Origin Resource Sharing (CORS) in
22
  CORS(app)
23
 
24
- # Een mapping van spaCy dependency-labels naar onze logische analyse-labels met uitleg
25
- DEP_MAP = {
26
  "nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."},
27
  "ROOT": {"label": "Predicato Verbale", "description": "Esprime l'azione o lo stato del soggetto."},
28
  "obj": {"label": "Complemento Oggetto", "description": "Indica l'oggetto diretto dell'azione del verbo."},
@@ -38,63 +38,63 @@ DEP_MAP = {
38
  "csubj": {"label": "Proposizione Subordinata Soggettiva", "description": "Frase che funge da soggetto del verbo della principale."}
39
  }
40
 
41
- def get_complement_type_with_details(token):
42
- """Verfijnt het complementtype op basis van het voorgaande voorzetsel en geeft details."""
43
- preposition = ""
44
- # Zoek naar een voorzetsel ('case') als een kind van het token
45
- for child in token.children:
46
- if child.dep_ == "case":
47
- preposition = child.text.lower()
48
  break
49
- # Fallback voor sommige structuren waar het voorzetsel een zuster is
50
- if not preposition and token.head.dep_ == 'obl':
51
- for child in token.head.children:
52
- if child.dep_ == "case":
53
- preposition = child.text.lower()
54
  break
55
 
56
- if preposition in ["di", "del", "dello", "della", "dei", "degli", "delle"]:
57
  return {"label": "Complemento di Specificazione", "description": "Risponde alla domanda 'di chi?', 'di che cosa?'."}
58
- if preposition in ["a", "al", "allo", "alla", "ai", "agli", "alle"]:
59
  return {"label": "Complemento di Termine", "description": "Risponde alla domanda 'a chi?', 'a che cosa?'."}
60
- if preposition in ["da", "dal", "dallo", "dalla", "dai", "dagli", "dalle"]:
61
- # Controleer op passieve constructie voor Complemento d'Agente
62
- if any(child.dep_ == 'aux:pass' for child in token.head.children):
63
  return {"label": "Complemento d'Agente", "description": "Indica da chi è compiuta l'azione in una frase passiva."}
64
  return {"label": "Complemento di Moto da Luogo", "description": "Indica il luogo da cui inizia un movimento."}
65
- if preposition in ["in", "nel", "nello", "nella", "nei", "negli", "nelle"]:
66
  return {"label": "Complemento di Stato in Luogo", "description": "Indica il luogo in cui si svolge un'azione o ci si trova."}
67
- if preposition in ["con", "col", "coi"]:
68
  return {"label": "Complemento di Compagnia o Mezzo", "description": "Indica la persona/animale con cui si compie l'azione o lo strumento utilizzato."}
69
- if preposition in ["su", "sul", "sullo", "sulla", "sui", "sugli", "sulle"]:
70
  return {"label": "Complemento di Argomento o Luogo", "description": "Indica l'argomento di cui si parla o il luogo su cui si trova qualcosa."}
71
- if preposition in ["per"]:
72
  return {"label": "Complemento di Fine o Causa", "description": "Indica lo scopo o la causa di un'azione."}
73
- if preposition in ["tra", "fra"]:
74
  return {"label": "Complemento di Luogo o Tempo (Partitivo)", "description": "Indica una posizione intermedia o una scelta all'interno di un gruppo."}
75
 
76
- # Standaard als geen specifiek voorzetsel wordt gevonden
77
  return {"label": "Complemento Indiretto", "description": "Fornisce un'informazione generica non classificata in modo più specifico."}
78
 
79
- def get_full_text(token):
80
- """Bouwt recursief de volledige tekst van een zinsdeel op, beginnend bij een hoofdtoken."""
81
- # Verzamel het hoofdtoken en de direct gerelateerde modifiers (determiners, adjectieven, voorzetsels)
82
- phrase_tokens = [token] + sorted([t for t in token.children if t.dep_ in ('det', 'amod', 'case', 'advmod')], key=lambda x: x.i)
83
- # Sorteer alle tokens op basis van hun positie in de zin om de juiste volgorde te krijgen
84
- phrase_tokens.sort(key=lambda x: x.i)
85
- return " ".join(t.text for t in phrase_tokens)
86
-
87
- def build_phrases_with_details(tokens):
88
- """Voegt tokens samen tot betekenisvolle grammaticale zinsdelen met gedetailleerde uitleg."""
89
- phrase_map = {}
90
 
91
- # Maak een map van belangrijke tokens (hoofden van zinsdelen)
92
- for token in tokens:
93
- # Filter onbelangrijke tokens uit die later worden samengevoegd
94
  if token.dep_ not in ['det', 'case', 'amod', 'punct', 'aux', 'cop', 'mark']:
95
- phrase_map[token.i] = {
96
- "text": get_full_text(token),
97
- # Voeg gedetailleerde grammaticale informatie toe met uitleg
98
  "token_details": {
99
  "lemma": token.lemma_,
100
  "pos": f"{token.pos_}: {spacy.explain(token.pos_)}",
@@ -105,25 +105,25 @@ def build_phrases_with_details(tokens):
105
  "token": token
106
  }
107
 
108
- analysis_result = []
109
- processed_indices = set()
110
 
111
- for index, phrase in phrase_map.items():
112
- if index in processed_indices:
113
  continue
114
 
115
- token = phrase['token']
116
  dep = token.dep_
117
- label_info = {}
118
 
119
  if dep == "ROOT":
120
- # Controleer op een naamwoordelijk gezegde (bv. "è bello")
121
- is_nominal = any(c.dep_ == 'cop' for c in token.children)
122
- if is_nominal:
123
  copula = [c for c in token.children if c.dep_ == 'cop'][0]
124
- predicate_name = get_full_text(token)
125
- # Voeg de copula apart toe
126
- analysis_result.append({
127
  "text": copula.text,
128
  "label_info": {"label": "Copula", "description": "Verbo 'essere' che collega il soggetto alla parte nominale."},
129
  "token_details": {
@@ -133,110 +133,110 @@ def build_phrases_with_details(tokens):
133
  "morph": str(copula.morph) if copula.morph else "Non disponibile"
134
  }
135
  })
136
- # Voeg het naamwoordelijk deel van het gezegde toe
137
- analysis_result.append({
138
- "text": predicate_name,
139
  "label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."},
140
- "token_details": phrase["token_details"]
141
  })
142
  else:
143
- # Het is een werkwoordelijk gezegde
144
- label_info = DEP_MAP.get(dep, {})
145
  elif dep == 'obl':
146
- # Gebruik de speciale functie om het type indirect complement te bepalen
147
- label_info = get_complement_type_with_details(token)
148
- elif dep in DEP_MAP:
149
- # Haal het label en de beschrijving op uit de map
150
- label_info = DEP_MAP[dep]
151
 
152
- # Voeg het geanalyseerde zinsdeel toe aan de resultatenlijst
153
- if label_info:
154
- phrase_to_add = {
155
- "text": phrase['text'],
156
- "label_info": label_info
157
  }
158
- # Voeg de token-details toe als ze bestaan
159
- if phrase.get("token_details"):
160
- phrase_to_add["token_details"] = phrase["token_details"]
161
- analysis_result.append(phrase_to_add)
162
 
163
- processed_indices.add(index)
164
 
165
- return analysis_result
166
 
167
- def analyze_clause_with_details(clause_tokens):
168
- """Analyseert een enkele (hoofd- of bij-)zin met details."""
169
- # Verwijder verbindingswoorden (markers) uit de analyse van de zinsdelen zelf
170
- tokens_in_clause = [t for t in clause_tokens if t.dep_ != 'mark']
171
- return build_phrases_with_details(tokens_in_clause)
172
 
173
  @app.route("/")
174
  def home():
175
- """Geeft een eenvoudig welkomstbericht voor de API-root."""
176
- return jsonify({"message": "API for logical analysis is running. Use the /api/analyze endpoint."})
177
 
178
  @app.route('/api/analyze', methods=['POST'])
179
- def analyze_sentence():
180
- """Hoofd-endpoint om een zin te ontvangen en de volledige logische analyse met details terug te sturen."""
181
  try:
182
- data = request.get_json()
183
- if not data or 'sentence' not in data:
184
- return jsonify({"error": "Sentence not provided"}), 400
185
 
186
- sentence = data['sentence']
187
- doc = nlp(sentence)
188
 
189
- main_clause_tokens = []
190
- subordinate_clauses = []
191
 
192
- # Identificeer en scheid bijzinnen
193
  for token in doc:
194
  if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj"]:
195
- sub_clause_tokens = list(token.subtree)
196
- sub_clause_type_info = DEP_MAP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Una frase che dipende da un'altra."})
197
 
198
- # Vind het inleidende woord (bv. "che", "quando", "perché")
199
- marker = [child for child in token.children if child.dep_ == 'mark']
200
- intro = marker[0].text if marker else ""
201
 
202
- subordinate_clauses.append({
203
- "type_info": sub_clause_type_info,
204
- "text": " ".join(t.text for t in sub_clause_tokens),
205
  "intro": intro,
206
- "analysis": analyze_clause_with_details(sub_clause_tokens)
207
  })
208
 
209
- # Bepaal de tokens van de hoofdzin door de tokens van de bijzinnen uit te sluiten
210
- subordinate_indices = {token.i for clause in subordinate_clauses for token in nlp(clause["text"])}
211
- main_clause_tokens = [token for token in doc if token.i not in subordinate_indices]
212
 
213
- # Extraheer Named Entities met uitleg
214
- named_entities = [{
215
  "text": ent.text,
216
  "label": ent.label_,
217
- "explanation": spacy.explain(ent.label_) # Zorg voor uitleg
218
  } for ent in doc.ents]
219
 
220
- # Stel de uiteindelijke analyse samen
221
- final_analysis = {
222
- "full_sentence": sentence,
223
  "main_clause": {
224
- "text": " ".join(t.text for t in main_clause_tokens if not t.is_punct),
225
- "analysis": analyze_clause_with_details(main_clause_tokens)
226
  },
227
- "subordinate_clauses": subordinate_clauses,
228
- "named_entities": named_entities
229
  }
230
 
231
- return jsonify(final_analysis)
232
 
233
  except Exception as e:
234
- # Verbeterde foutafhandeling
235
- print(f"Error during analysis: {e}")
236
  traceback.print_exc()
237
- return jsonify({"error": "An internal error occurred."}), 500
238
 
239
  if __name__ == '__main__':
240
- # Haal de poort op uit de omgevingsvariabelen voor implementatiegemak
241
- port = int(os.environ.get("PORT", 8080))
242
- app.run(host="0.0.0.0", port=port, debug=True)
 
4
  import spacy
5
  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
 
18
+ # Inizializza l'app Flask
19
  app = Flask(__name__)
20
 
21
+ # Abilita la Condivisione delle Risorse tra Origini Diverse (CORS)
22
  CORS(app)
23
 
24
+ # Mappatura delle etichette di dipendenza di spaCy alle nostre etichette di analisi logica con spiegazioni
25
+ MAPPA_DEP = {
26
  "nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."},
27
  "ROOT": {"label": "Predicato Verbale", "description": "Esprime l'azione o lo stato del soggetto."},
28
  "obj": {"label": "Complemento Oggetto", "description": "Indica l'oggetto diretto dell'azione del verbo."},
 
38
  "csubj": {"label": "Proposizione Subordinata Soggettiva", "description": "Frase che funge da soggetto del verbo della principale."}
39
  }
40
 
41
+ def ottieni_tipo_complemento_con_dettagli(token):
42
+ """Affina il tipo di complemento basandosi sulla preposizione precedente e fornisce dettagli."""
43
+ preposizione = ""
44
+ # Cerca una preposizione ('case') come figlio del token
45
+ for figlio in token.children:
46
+ if figlio.dep_ == "case":
47
+ preposizione = figlio.text.lower()
48
  break
49
+ # Soluzione alternativa per alcune strutture dove la preposizione è un fratello
50
+ if not preposizione and token.head.dep_ == 'obl':
51
+ for figlio in token.head.children:
52
+ if figlio.dep_ == "case":
53
+ preposizione = figlio.text.lower()
54
  break
55
 
56
+ if preposizione in ["di", "del", "dello", "della", "dei", "degli", "delle"]:
57
  return {"label": "Complemento di Specificazione", "description": "Risponde alla domanda 'di chi?', 'di che cosa?'."}
58
+ if preposizione in ["a", "al", "allo", "alla", "ai", "agli", "alle"]:
59
  return {"label": "Complemento di Termine", "description": "Risponde alla domanda 'a chi?', 'a che cosa?'."}
60
+ if preposizione in ["da", "dal", "dallo", "dalla", "dai", "dagli", "dalle"]:
61
+ # Controlla la costruzione passiva per il Complemento d'Agente
62
+ if any(figlio.dep_ == 'aux:pass' for figlio in token.head.children):
63
  return {"label": "Complemento d'Agente", "description": "Indica da chi è compiuta l'azione in una frase passiva."}
64
  return {"label": "Complemento di Moto da Luogo", "description": "Indica il luogo da cui inizia un movimento."}
65
+ if preposizione in ["in", "nel", "nello", "nella", "nei", "negli", "nelle"]:
66
  return {"label": "Complemento di Stato in Luogo", "description": "Indica il luogo in cui si svolge un'azione o ci si trova."}
67
+ if preposizione in ["con", "col", "coi"]:
68
  return {"label": "Complemento di Compagnia o Mezzo", "description": "Indica la persona/animale con cui si compie l'azione o lo strumento utilizzato."}
69
+ if preposizione in ["su", "sul", "sullo", "sulla", "sui", "sugli", "sulle"]:
70
  return {"label": "Complemento di Argomento o Luogo", "description": "Indica l'argomento di cui si parla o il luogo su cui si trova qualcosa."}
71
+ if preposizione in ["per"]:
72
  return {"label": "Complemento di Fine o Causa", "description": "Indica lo scopo o la causa di un'azione."}
73
+ if preposizione in ["tra", "fra"]:
74
  return {"label": "Complemento di Luogo o Tempo (Partitivo)", "description": "Indica una posizione intermedia o una scelta all'interno di un gruppo."}
75
 
76
+ # Valore predefinito se non viene trovata una preposizione specifica
77
  return {"label": "Complemento Indiretto", "description": "Fornisce un'informazione generica non classificata in modo più specifico."}
78
 
79
+ def ottieni_testo_completo(token):
80
+ """Costruisce ricorsivamente il testo completo di un sintagma, partendo da un token principale."""
81
+ # Raccoglie il token principale e i modificatori direttamente correlati (determinanti, aggettivi, preposizioni)
82
+ token_sintagma = [token] + sorted([t for t in token.children if t.dep_ in ('det', 'amod', 'case', 'advmod')], key=lambda x: x.i)
83
+ # Ordina tutti i token in base alla loro posizione nella frase per ottenere l'ordine corretto
84
+ token_sintagma.sort(key=lambda x: x.i)
85
+ return " ".join(t.text for t in token_sintagma)
86
+
87
+ def costruisci_sintagmi_con_dettagli(lista_token):
88
+ """Aggrega i token in sintagmi grammaticali significativi con spiegazioni dettagliate."""
89
+ mappa_sintagmi = {}
90
 
91
+ # Crea una mappa di token importanti (le teste dei sintagmi)
92
+ for token in lista_token:
93
+ # Esclude i token non importanti che verranno uniti in seguito
94
  if token.dep_ not in ['det', 'case', 'amod', 'punct', 'aux', 'cop', 'mark']:
95
+ mappa_sintagmi[token.i] = {
96
+ "text": ottieni_testo_completo(token),
97
+ # Aggiunge informazioni grammaticali dettagliate con spiegazioni
98
  "token_details": {
99
  "lemma": token.lemma_,
100
  "pos": f"{token.pos_}: {spacy.explain(token.pos_)}",
 
105
  "token": token
106
  }
107
 
108
+ risultato_analisi = []
109
+ indici_elaborati = set()
110
 
111
+ for indice, sintagma in mappa_sintagmi.items():
112
+ if indice in indici_elaborati:
113
  continue
114
 
115
+ token = sintagma['token']
116
  dep = token.dep_
117
+ info_etichetta = {}
118
 
119
  if dep == "ROOT":
120
+ # Controlla la presenza di un predicato nominale (es. "è bello")
121
+ e_nominale = any(c.dep_ == 'cop' for c in token.children)
122
+ if e_nominale:
123
  copula = [c for c in token.children if c.dep_ == 'cop'][0]
124
+ nome_del_predicato = ottieni_testo_completo(token)
125
+ # Aggiunge la copula separatamente
126
+ risultato_analisi.append({
127
  "text": copula.text,
128
  "label_info": {"label": "Copula", "description": "Verbo 'essere' che collega il soggetto alla parte nominale."},
129
  "token_details": {
 
133
  "morph": str(copula.morph) if copula.morph else "Non disponibile"
134
  }
135
  })
136
+ # Aggiunge la parte nominale del predicato
137
+ risultato_analisi.append({
138
+ "text": nome_del_predicato,
139
  "label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."},
140
+ "token_details": sintagma["token_details"]
141
  })
142
  else:
143
+ # È un predicato verbale
144
+ info_etichetta = MAPPA_DEP.get(dep, {})
145
  elif dep == 'obl':
146
+ # Usa la funzione speciale per determinare il tipo di complemento indiretto
147
+ info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
148
+ elif dep in MAPPA_DEP:
149
+ # Recupera l'etichetta e la descrizione dalla mappa
150
+ info_etichetta = MAPPA_DEP[dep]
151
 
152
+ # Aggiunge il sintagma analizzato alla lista dei risultati
153
+ if info_etichetta:
154
+ sintagma_da_aggiungere = {
155
+ "text": sintagma['text'],
156
+ "label_info": info_etichetta
157
  }
158
+ # Aggiunge i dettagli del token se esistono
159
+ if sintagma.get("token_details"):
160
+ sintagma_da_aggiungere["token_details"] = sintagma["token_details"]
161
+ risultato_analisi.append(sintagma_da_aggiungere)
162
 
163
+ indici_elaborati.add(indice)
164
 
165
+ return risultato_analisi
166
 
167
+ def analizza_proposizione_con_dettagli(token_proposizione):
168
+ """Analizza una singola proposizione (principale o subordinata) con dettagli."""
169
+ # Rimuove le congiunzioni (marcatori) dall'analisi dei sintagmi stessi
170
+ token_nella_proposizione = [t for t in token_proposizione if t.dep_ != 'mark']
171
+ return costruisci_sintagmi_con_dettagli(token_nella_proposizione)
172
 
173
  @app.route("/")
174
  def home():
175
+ """Restituisce un semplice messaggio di benvenuto per la radice dell'API."""
176
+ return jsonify({"messaggio": "L'API per l'analisi logica è in esecuzione. Usa l'endpoint /api/analyze."})
177
 
178
  @app.route('/api/analyze', methods=['POST'])
179
+ def analizza_frase():
180
+ """Endpoint principale per ricevere una frase e restituire l'analisi logica completa con dettagli."""
181
  try:
182
+ dati = request.get_json()
183
+ if not dati or 'sentence' not in dati:
184
+ return jsonify({"errore": "Frase non fornita"}), 400
185
 
186
+ frase = dati['sentence']
187
+ doc = nlp(frase)
188
 
189
+ token_proposizione_principale = []
190
+ proposizioni_subordinate = []
191
 
192
+ # Identifica e separa le proposizioni subordinate
193
  for token in doc:
194
  if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj"]:
195
+ token_proposizione_subordinata = list(token.subtree)
196
+ info_tipo_subordinata = MAPPA_DEP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Una frase che dipende da un'altra."})
197
 
198
+ # Trova la parola introduttiva (es. "che", "quando", "perché")
199
+ marcatore = [figlio for figlio in token.children if figlio.dep_ == 'mark']
200
+ intro = marcatore[0].text if marcatore else ""
201
 
202
+ proposizioni_subordinate.append({
203
+ "type_info": info_tipo_subordinata,
204
+ "text": " ".join(t.text for t in token_proposizione_subordinata),
205
  "intro": intro,
206
+ "analysis": analizza_proposizione_con_dettagli(token_proposizione_subordinata)
207
  })
208
 
209
+ # Determina i token della proposizione principale escludendo quelli delle subordinate
210
+ indici_subordinate = {token.i for prop in proposizioni_subordinate for token in nlp(prop["text"])}
211
+ token_proposizione_principale = [token for token in doc if token.i not in indici_subordinate]
212
 
213
+ # Estrae le Entità Nominate (Named Entities) con spiegazione
214
+ entita_nominate = [{
215
  "text": ent.text,
216
  "label": ent.label_,
217
+ "explanation": spacy.explain(ent.label_) # Fornisce la spiegazione
218
  } for ent in doc.ents]
219
 
220
+ # Compone l'analisi finale
221
+ analisi_finale = {
222
+ "full_sentence": frase,
223
  "main_clause": {
224
+ "text": " ".join(t.text for t in token_proposizione_principale if not t.is_punct),
225
+ "analysis": analizza_proposizione_con_dettagli(token_proposizione_principale)
226
  },
227
+ "subordinate_clauses": proposizioni_subordinate,
228
+ "named_entities": entita_nominate
229
  }
230
 
231
+ return jsonify(analisi_finale)
232
 
233
  except Exception as e:
234
+ # Gestione migliorata degli errori
235
+ print(f"Errore durante l'analisi: {e}")
236
  traceback.print_exc()
237
+ return jsonify({"errore": "Si è verificato un errore interno."}), 500
238
 
239
  if __name__ == '__main__':
240
+ # Ottiene la porta dalle variabili d'ambiente per facilitare il deployment
241
+ porta = int(os.environ.get("PORT", 8080))
242
+ app.run(host="0.0.0.0", port=porta, debug=True)