Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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\
|
| 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:
|
| 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 |
-
"
|
| 83 |
-
"
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
-
"
|
| 87 |
-
"
|
| 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 |
-
|
| 129 |
-
return
|
| 130 |
|
| 131 |
def traduci_morfologia(morph_str: str) -> str:
|
| 132 |
if not morph_str or morph_str == "___": return "Non disponibile"
|
| 133 |
-
|
| 134 |
-
|
|
|
|
| 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.
|
| 140 |
-
return ", ".join(sorted(list(
|
| 141 |
-
|
| 142 |
|
| 143 |
def ottieni_tipo_complemento_con_dettagli(token):
|
| 144 |
-
|
| 145 |
-
if not
|
|
|
|
| 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 |
-
|
| 167 |
-
|
| 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
|
| 176 |
-
|
| 177 |
"""
|
|
|
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
# Raccoglie
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
#
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
return testo_sintagma, indici_usati
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
risultato_analisi = []
|
| 209 |
indici_elaborati = set()
|
| 210 |
|
| 211 |
-
#
|
| 212 |
-
|
| 213 |
|
| 214 |
for token in tokens_proposizione:
|
| 215 |
-
if token.i in indici_elaborati or token.dep_ in
|
| 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 |
-
|
| 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 |
-
#
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
return risultato_analisi
|
| 272 |
|
| 273 |
-
def analizza_proposizione_con_dettagli(
|
| 274 |
-
|
| 275 |
-
return costruisci_sintagmi_con_dettagli(
|
| 276 |
|
| 277 |
# ------------------------------
|
| 278 |
# Routes
|
| 279 |
# ------------------------------
|
| 280 |
@app.route("/")
|
| 281 |
def home():
|
| 282 |
-
status = "ok" if nlp
|
| 283 |
return jsonify({
|
| 284 |
-
"messaggio": "API analisi logica in esecuzione",
|
| 285 |
-
"
|
| 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
|
| 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 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 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 |
-
"
|
| 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": "1ª", "2": "2ª", "3": "3ª", "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo",
|
| 75 |
+
"Art": "Articolativo", "Yes": "Sì", "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,
|