Spaces:
Sleeping
Sleeping
File size: 18,145 Bytes
42693f7 31edf0b 42693f7 699c193 42693f7 8153da1 870c988 8153da1 699c193 870c988 42693f7 699c193 42693f7 699c193 e90e953 5a3f6ab 699c193 5a3f6ab 699c193 8153da1 699c193 5a3f6ab 699c193 8153da1 5a3f6ab 96e4672 31edf0b 8153da1 31edf0b 8153da1 699c193 42693f7 699c193 96e4672 42693f7 8153da1 96e4672 8153da1 96e4672 4f5a1e9 42693f7 8153da1 699c193 8153da1 699c193 8153da1 42693f7 96e4672 699c193 8153da1 699c193 8153da1 699c193 8153da1 96e4672 699c193 96e4672 699c193 96e4672 699c193 96e4672 699c193 e90e953 96e4672 4f5a1e9 699c193 96e4672 4f5a1e9 8153da1 4f5a1e9 699c193 4f5a1e9 699c193 96e4672 699c193 31edf0b 699c193 31edf0b 4f5a1e9 699c193 96e4672 699c193 8153da1 4f5a1e9 8153da1 4f5a1e9 699c193 96e4672 8153da1 699c193 31edf0b 699c193 96e4672 699c193 8153da1 4f5a1e9 96e4672 699c193 96e4672 4f5a1e9 699c193 42693f7 96e4672 42693f7 96e4672 42693f7 96e4672 42693f7 8153da1 96e4672 699c193 96e4672 e90e953 699c193 42693f7 699c193 96e4672 e90e953 96e4672 4f5a1e9 96e4672 8153da1 4f5a1e9 96e4672 4f5a1e9 96e4672 4f5a1e9 96e4672 31edf0b e90e953 31edf0b 8153da1 96e4672 4f5a1e9 8153da1 96e4672 4f5a1e9 96e4672 8153da1 4f5a1e9 699c193 96e4672 42693f7 4f5a1e9 8153da1 4f5a1e9 96e4672 699c193 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 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 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
import os
from flask import Flask, request, jsonify
from flask_cors import CORS
import spacy
import traceback
# --- CARICAMENTO MODELLO ---
try:
nlp = spacy.load("it_core_news_lg")
except OSError:
raise RuntimeError(
"Impossibile trovare il modello 'it_core_news_lg'. "
"Assicurati che sia elencato e installato nel tuo requirements.txt.\n"
"Comando per installarlo localmente: python -m spacy download it_core_news_lg"
)
# --- INIZIALIZZAZIONE APP ---
app = Flask(__name__)
CORS(app)
# --- MAPPE DI TRADUZIONE / SPIEGAZIONI ---
SPIEGAZIONI_POS_IT = {
"ADJ": "Aggettivo", "ADP": "Preposizione", "ADV": "Avverbio", "AUX": "Ausiliare",
"CONJ": "Congiunzione", "CCONJ": "Congiunzione Coordinante", "SCONJ": "Congiunzione Subordinante",
"DET": "Determinante", "INTJ": "Interiezione", "NOUN": "Sostantivo", "NUM": "Numerale",
"PART": "Particella", "PRON": "Pronome", "PROPN": "Nome Proprio", "PUNCT": "Punteggiatura",
"SPACE": "Spazio", "SYM": "Simbolo", "VERB": "Verbo", "X": "Altro",
}
SPIEGAZIONI_ENT_IT = {
"PER": "Persona: Nomi di persone reali o fittizie.",
"LOC": "Luogo: Nomi di luoghi geografici come paesi, città, stati.",
"ORG": "Organizzazione: Nomi di aziende, istituzioni, governi.",
"MISC": "Miscellanea: Entità che non rientrano nelle altre categorie (es. eventi, nazionalità, prodotti)."
}
TRADUZIONI_MORFOLOGIA = {
# chiavi
"Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo",
"Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome",
"Clitic": "Clitico", "Definite": "Definizione", "Degree": "Grado",
"Case": "Caso", "Poss": "Possessivo", "Reflex": "Riflessivo",
"Aspect": "Aspetto", "Voice": "Voce", "Polarity": "Polarità",
# valori
"Masc": "Maschile", "Fem": "Femminile", "Sing": "Singolare", "Plur": "Plurale",
"Ind": "Indicativo", "Sub": "Congiuntivo", "Cnd": "Condizionale", "Imp": "Imperativo",
"Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Pqp": "Trapassato",
"Fin": "Finita", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio",
"Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo",
"Art": "Articolativo", "Indf": "Indeterminato", "Yes": "Sì", "No": "No",
"Abs": "Assoluto", "Cmp": "Comparativo", "Sup": "Superlativo",
"Nom": "Nominativo", "Acc": "Accusativo", "Gen": "Genitivo", "Dat": "Dativo",
"Perf": "Perfetto", "Prog": "Progressivo",
"Act": "Attiva", "Pass": "Passiva",
}
MAPPA_DEP = {
"nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."},
"nsubj:pass": {"label": "Soggetto (Passivo)", "description": "Soggetto in una costruzione passiva."},
"ROOT": {"label": "Predicato Verbale", "description": "Esprime l'azione o lo stato del soggetto."},
"obj": {"label": "Complemento Oggetto", "description": "Indica l'oggetto diretto dell'azione del verbo."},
"iobj": {"label": "Complemento di Termine", "description": "Indica a chi o a cosa è destinata l'azione."},
"obl": {"label": "Complemento Indiretto", "description": "Fornisce informazioni aggiuntive come luogo, tempo, modo, causa, etc."},
"nmod": {"label": "Complemento di Specificazione", "description": "Specifica o chiarisce il significato del nome a cui si riferisce."},
"amod": {"label": "Attributo", "description": "Aggettivo che qualifica un nome."},
"advmod": {"label": "Complemento Avverbiale", "description": "Modifica il significato di un verbo, aggettivo o altro avverbio."},
"appos": {"label": "Apposizione", "description": "Nome che ne chiarisce un altro."},
"acl:relcl": {"label": "Proposizione Subordinata Relativa", "description": "Frase che espande un nome, introdotta da un pronome relativo."},
"advcl": {"label": "Proposizione Subordinata Avverbiale", "description": "Frase che funziona come un avverbio, modificando il verbo della principale."},
"ccomp": {"label": "Proposizione Subordinata Oggettiva", "description": "Frase che funge da complemento oggetto del verbo della principale."},
"csubj": {"label": "Proposizione Subordinata Soggettiva", "description": "Frase che funge da soggetto del verbo della principale."},
"xcomp": {"label": "Complemento Predicativo", "description": "Complemento che completa il significato del verbo."},
"acl": {"label": "Modificatore Relativo", "description": "Clausola che modifica un nome."},
"compound": {"label": "Composto", "description": "Parte di un composto nominale."},
"flat": {"label": "Nome Piatto", "description": "Parte di un nome proprio o espressione fissa."},
"conj": {"label": "Congiunzione Coordinata", "description": "Elemento coordinato con un altro."},
"cc": {"label": "Congiunzione Coordinante", "description": "Congiunzione che collega elementi coordinati."},
"parataxis": {"label": "Paratassi", "description": "Frasi coordinate senza connettore esplicito."}
}
# --- FUNZIONI DI UTILITÀ ---
def spiega_in_italiano(tag, tipo='pos'):
"""
Fornisce spiegazione per POS o entità.
- tipo == 'pos' => tag dovrebbe essere token.pos_ (es. VERB, NOUN)
- tipo == 'ent' => ent label (PER, LOC, ORG...)
"""
if tipo == 'pos':
key = tag.upper().split(":")[0] # normalizza caso e rimuove eventuali dettagli
return SPIEGAZIONI_POS_IT.get(key, key)
if tipo == 'ent':
return SPIEGAZIONI_ENT_IT.get(tag, tag)
return tag
def traduci_morfologia_from_token(token):
"""
Usa token.morph (meglio token.morph.to_dict()) per costruire una descrizione leggibile.
"""
try:
morph_dict = token.morph.to_dict()
except Exception:
# Fallback alla stringa originale
morph_str = str(token.morph)
if not morph_str:
return "Non disponibile"
parti = morph_str.split("|")
mappate = []
for parte in parti:
if "=" in parte:
k, v = parte.split("=", 1)
mappate.append(f"{TRADUZIONI_MORFOLOGIA.get(k,k)}: {TRADUZIONI_MORFOLOGIA.get(v, v)}")
else:
mappate.append(TRADUZIONI_MORFOLOGIA.get(parte, parte))
return ", ".join(mappate) if mappate else "Non disponibile"
parti_tradotte = []
for k, v in sorted(morph_dict.items()):
k_tr = TRADUZIONI_MORFOLOGIA.get(k, k)
if isinstance(v, (list, tuple)):
v_str = ", ".join(TRADUZIONI_MORFOLOGIA.get(x, x) for x in v)
else:
v_str = TRADUZIONI_MORFOLOGIA.get(v, v)
parti_tradotte.append(f"{k_tr}: {v_str}")
return ", ".join(parti_tradotte) if parti_tradotte else "Non disponibile"
def get_verb_phrase(token):
"""
Costruisce la 'verb phrase' completa: aggiunge ausiliari, negazioni, particelle legate.
Ordina i token per indice per mantenere la sequenza corretta.
"""
verb_related = []
# includi token stesso se è verbo o copula
if token.pos_ in ("VERB", "AUX") or token.dep_ in ("cop", "ROOT"):
verb_related.append(token)
# cerca ausiliari, negazioni e copula fra i figli e nella testa (se la testa è verbo)
for t in list(token.children):
if t.dep_ in ('aux', 'aux:pass', 'neg', 'cop', 'prt'):
verb_related.append(t)
# talvolta l'ausiliare è head (in casi particolari); includi ausiliari nella testa che hanno head==token or viceversa
# includi anche eventuali elementi nella subtree stretta che sono parte del verbo
for t in token.subtree:
if t.dep_ in ('aux', 'aux:pass', 'neg', 'cop', 'prt') and t not in verb_related:
verb_related.append(t)
# rimuovi duplicati e ordina
verb_related = sorted(set(verb_related), key=lambda x: x.i)
return " ".join(t.text for t in verb_related).strip() or token.text
def ottieni_tipo_complemento_con_dettagli(token):
preposizione = ""
for figlio in token.children:
if figlio.dep_ == "case":
preposizione = figlio.text.lower()
break
if not preposizione and token.dep_ == 'obl':
for figlio in token.head.children:
if figlio.dep_ == "case" and figlio.head == token:
preposizione = figlio.text.lower()
break
mappa_preposizioni = {
"di": "Complemento di Specificazione",
"del": "Complemento di Specificazione", "dello": "Complemento di Specificazione",
"della": "Complemento di Specificazione", "dei": "Complemento di Specificazione",
"a": "Complemento di Termine", "al": "Complemento di Termine",
"da": "Complemento di Moto da Luogo", "dal": "Complemento di Moto da Luogo",
"in": "Complemento di Stato in Luogo", "nel": "Complemento di Stato in Luogo",
"con": "Complemento di Compagnia o Mezzo", "su": "Complemento di Argomento o Luogo",
"per": "Complemento di Fine o Causa", "tra": "Complemento di Luogo o Tempo (Partitivo)",
"fra": "Complemento di Luogo o Tempo (Partitivo)",
}
label = mappa_preposizioni.get(preposizione, "Complemento Indiretto")
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."
if preposizione.startswith("da") and any(figlio.dep_ in ('aux:pass','aux') for figlio in token.head.children):
label = "Complemento d'Agente"
description = "Indica da chi è compiuta l'azione in una frase passiva."
return {"label": label, "description": description}
def ottieni_testo_completo(token):
"""
Raccoglie il sintagma completo includendo determinanti, aggettivi, apposizioni, complementi stretti.
"""
def raccogli_figli(t):
figli = []
for f in t.children:
if f.dep_ in ('det', 'amod', 'case', 'advmod', 'nmod', 'appos', 'acl', 'compound', 'flat', 'nummod'):
figli.append(f)
figli.extend(raccogli_figli(f))
return figli
token_sintagma = [token] + raccogli_figli(token)
token_sintagma = sorted(set(token_sintagma), key=lambda x: x.i)
return " ".join(t.text for t in token_sintagma if not t.is_punct).strip()
def costruisci_sintagmi_con_dettagli(lista_token):
"""
Costruisce la lista di sintagmi (soggetto, predicato, complementi) con dettagli per ogni token significativo.
Non scartiamo ausiliari e copula: li rappresentiamo opportunamente.
"""
mappa_sintagmi = {}
# selezioniamo token utili (scartiamo solo punteggiatura e spazi)
for token in lista_token:
if token.is_punct or token.is_space:
continue
mappa_sintagmi[token.i] = {
"text": ottieni_testo_completo(token),
"token_details": {
"text": token.text,
"lemma": token.lemma_,
"pos": token.pos_,
"pos_explanation": spiega_in_italiano(token.pos_, 'pos'),
"tag": token.tag_,
"morph_raw": str(token.morph),
"morph": traduci_morfologia_from_token(token)
},
"label_info": {},
"token": token
}
risultato_analisi = []
indici_elaborati = set()
for indice, sintagma in sorted(mappa_sintagmi.items()):
if indice in indici_elaborati:
continue
token = sintagma['token']
dep = token.dep_
info_etichetta = MAPPA_DEP.get(dep, {"label": dep, "description": "Relazione non mappata."})
# gestione ROOT e predicato nominale con copula
if dep == "ROOT":
# trova eventuali copule collegate (cop o aux:cop)
copula_children = [c for c in token.children if c.dep_ in ('cop', 'aux:cop')]
if copula_children:
cop = copula_children[0]
# copula come verbo
risultato_analisi.append({
"text": cop.text,
"label_info": {"label": "Copula", "description": "Verbo 'essere' che collega soggetto e parte nominale."},
"token_details": {
"lemma": cop.lemma_,
"pos": cop.pos_,
"pos_explanation": spiega_in_italiano(cop.pos_, 'pos'),
"tag": cop.tag_,
"morph": traduci_morfologia_from_token(cop),
"verb_phrase": get_verb_phrase(cop)
}
})
# parte nominale (il nome/aggettivo che segue)
risultato_analisi.append({
"text": sintagma["text"],
"label_info": {"label": "Parte Nominale del Predicato", "description": "Parte nominale che descrive il soggetto."},
"token_details": sintagma["token_details"]
})
indici_elaborati.add(indice)
continue
else:
# ROOT come verbo principale: mostriamo verb phrase completa
sintagma_da_aggiungere = {
"text": sintagma['text'],
"label_info": info_etichetta,
"token_details": dict(sintagma['token_details'])
}
# se è verbo, aggiungi la verb_phrase
if token.pos_ in ("VERB", "AUX"):
sintagma_da_aggiungere["token_details"]["verb_phrase"] = get_verb_phrase(token)
sintagma_da_aggiungere["token_details"]["verb_morph"] = traduci_morfologia_from_token(token)
risultato_analisi.append(sintagma_da_aggiungere)
indici_elaborati.add(indice)
continue
# gestione complementi obl e agent
if dep in ('obl', 'obl:agent', 'obl:mod'):
info_etichetta = ottieni_tipo_complemento_con_dettagli(token)
if dep == 'nsubj:pass':
info_etichetta = MAPPA_DEP.get('nsubj:pass', MAPPA_DEP['nsubj'])
sintagma_da_aggiungere = {
"text": sintagma['text'],
"label_info": info_etichetta
}
if sintagma.get("token_details"):
# se è un verbo o ausiliare aggiungiamo la verb_phrase
if token.pos_ in ("VERB", "AUX"):
sintagma['token_details']["verb_phrase"] = get_verb_phrase(token)
sintagma['token_details']["verb_morph"] = traduci_morfologia_from_token(token)
sintagma_da_aggiungere["token_details"] = sintagma["token_details"]
risultato_analisi.append(sintagma_da_aggiungere)
indici_elaborati.add(indice)
# rimuovi duplicati (testo)
risultato_unico = []
testi_visti = set()
for item in risultato_analisi:
if item['text'] not in testi_visti:
risultato_unico.append(item)
testi_visti.add(item['text'])
return risultato_unico
def analizza_proposizione_con_dettagli(token_proposizione):
token_nella_proposizione = [t for t in token_proposizione if not t.is_punct and not t.is_space]
return costruisci_sintagmi_con_dettagli(token_nella_proposizione)
# --- ENDPOINT ---
@app.route("/")
def home():
return jsonify({"messaggio": "L'API per l'analisi logica è in esecuzione. Usa l'endpoint /api/analyze."})
@app.route('/api/analyze', methods=['POST'])
def analizza_frase():
try:
dati = request.get_json()
if not dati or 'sentence' not in dati:
return jsonify({"errore": "Frase non fornita"}), 400
frase = dati['sentence'].strip()
if not frase:
return jsonify({"errore": "Frase vuota"}), 400
doc = nlp(frase)
proposizioni_subordinate = []
indici_subordinate = set()
for token in doc:
if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj", "xcomp", "acl", "parataxis", "parataxis:rel"]:
token_proposizione_subordinata = list(token.subtree)
for t in token_proposizione_subordinata:
indici_subordinate.add(t.i)
info_tipo_subordinata = MAPPA_DEP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Una frase che dipende da un'altra."})
marcatore = [figlio for figlio in token.children if figlio.dep_ == 'mark']
intro = marcatore[0].text if marcatore else ""
proposizioni_subordinate.append({
"type_info": info_tipo_subordinata,
"text": " ".join(t.text for t in token_proposizione_subordinata if not t.is_punct and not t.is_space).strip(),
"intro": intro,
"analysis": analizza_proposizione_con_dettagli(token_proposizione_subordinata)
})
token_proposizione_principale = [token for token in doc if token.i not in indici_subordinate]
entita_nominate = [{
"text": ent.text,
"label": ent.label_,
"explanation": spiega_in_italiano(ent.label_, 'ent')
} for ent in doc.ents]
# Rimuovi duplicati dalle entità
entita_unica = []
testi_ent_visti = set()
for ent in entita_nominate:
if ent['text'] not in testi_ent_visti:
entita_unica.append(ent)
testi_ent_visti.add(ent['text'])
analisi_finale = {
"full_sentence": frase,
"main_clause": {
"text": " ".join(t.text for t in token_proposizione_principale if not t.is_punct and not t.is_space).strip(),
"analysis": analizza_proposizione_con_dettagli(token_proposizione_principale)
},
"subordinate_clauses": proposizioni_subordinate,
"named_entities": entita_unica
}
return jsonify(analisi_finale)
except Exception as e:
traceback.print_exc()
return jsonify({"errore": "Si è verificato un errore interno.", "dettagli": str(e)}), 500
if __name__ == '__main__':
porta = int(os.environ.get("PORT", 8080))
app.run(host="0.0.0.0", port=porta, debug=False, threaded=True)
|