import os from flask import Flask, request, jsonify from flask_cors import CORS import spacy import traceback # --- SEZIONE CARICAMENTO MODELLO --- try: # Carica il modello italiano di spaCy nlp = spacy.load("it_core_news_sm") except OSError: raise RuntimeError( "Impossibile trovare il modello 'it_core_news_sm'. " "Assicurati che sia elencato e installato dal tuo file requirements.txt." ) # --- FINE SEZIONE --- # Inizializza l'app Flask app = Flask(__name__) CORS(app) # --- INIZIO SEZIONE TRADUZIONI --- 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)." } # NUOVA SEZIONE: Dizionario per le traduzioni morfologiche TRADUZIONI_MORFOLOGIA = { # Chiavi "Gender": "Genere", "Number": "Numero", "Mood": "Modo", "Tense": "Tempo", "Person": "Persona", "VerbForm": "Forma del Verbo", "PronType": "Tipo di Pronome", "Clitic": "Clitico", "Definite": "Definitezza", "Degree": "Grado", # Valori "Masc": "Maschile", "Fem": "Femminile", "Sing": "Singolare", "Plur": "Plurale", "Ind": "Indicativo", "Sub": "Congiuntivo", "Cnd": "Condizionale", "Imp": "Imperativo", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio", "Pres": "Presente", "Past": "Passato", "Fut": "Futuro", "Imp": "Imperfetto", "1": "1ª", "2": "2ª", "3": "3ª", "Fin": "Finita", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio", "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo", "Art": "Articolativo", "Yes": "Sì", "Ind": "Indeterminato", "Def": "Determinato", "Abs": "Assoluto", "Cmp": "Comparativo", } def spiega_in_italiano(tag, tipo='pos'): """Fornisce una spiegazione in italiano per un tag POS o di entità.""" if tipo == 'pos': return SPIEGAZIONI_POS_IT.get(tag, tag) if tipo == 'ent': return SPIEGAZIONI_ENT_IT.get(tag, tag) return tag def traduci_morfologia(morph_str): """Traduce la stringa morfologica in un formato leggibile in italiano.""" if not morph_str: return "Non disponibile" parti = morph_str.split('|') parti_tradotte = [] for parte in parti: if '=' in parte: chiave, valore = parte.split('=', 1) chiave_tradotta = TRADUZIONI_MORFOLOGIA.get(chiave, chiave) valore_tradotto = TRADUZIONI_MORFOLOGIA.get(valore, valore) parti_tradotte.append(f"{chiave_tradotta}: {valore_tradotto}") else: # Gestisce i casi in cui non c'è una coppia chiave=valore parti_tradotte.append(TRADUZIONI_MORFOLOGIA.get(parte, parte)) return ", ".join(parti_tradotte) # --- FINE SEZIONE TRADUZIONI --- MAPPA_DEP = { "nsubj": {"label": "Soggetto", "description": "Indica chi o cosa compie l'azione o si trova in un certo stato."}, "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."} } 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.head.dep_ == 'obl': for figlio in token.head.children: if figlio.dep_ == "case": preposizione = figlio.text.lower() break if preposizione in ["di", "del", "dello", "della", "dei", "degli", "delle"]: return {"label": "Complemento di Specificazione", "description": "Risponde alla domanda 'di chi?', 'di che cosa?'."} if preposizione in ["a", "al", "allo", "alla", "ai", "agli", "alle"]: return {"label": "Complemento di Termine", "description": "Risponde alla domanda 'a chi?', 'a che cosa?'."} if preposizione in ["da", "dal", "dallo", "dalla", "dai", "dagli", "dalle"]: if any(figlio.dep_ == 'aux:pass' for figlio in token.head.children): return {"label": "Complemento d'Agente", "description": "Indica da chi è compiuta l'azione in una frase passiva."} return {"label": "Complemento di Moto da Luogo", "description": "Indica il luogo da cui inizia un movimento."} if preposizione in ["in", "nel", "nello", "nella", "nei", "negli", "nelle"]: return {"label": "Complemento di Stato in Luogo", "description": "Indica il luogo in cui si svolge un'azione o ci si trova."} if preposizione in ["con", "col", "coi"]: return {"label": "Complemento di Compagnia o Mezzo", "description": "Indica la persona/animale con cui si compie l'azione o lo strumento utilizzato."} if preposizione in ["su", "sul", "sullo", "sulla", "sui", "sugli", "sulle"]: return {"label": "Complemento di Argomento o Luogo", "description": "Indica l'argomento di cui si parla o il luogo su cui si trova qualcosa."} if preposizione in ["per"]: return {"label": "Complemento di Fine o Causa", "description": "Indica lo scopo o la causa di un'azione."} if preposizione in ["tra", "fra"]: return {"label": "Complemento di Luogo o Tempo (Partitivo)", "description": "Indica una posizione intermedia o una scelta all'interno di un gruppo."} return {"label": "Complemento Indiretto", "description": "Fornisce un'informazione generica non classificata in modo più specifico."} def ottieni_testo_completo(token): token_sintagma = [token] + sorted([t for t in token.children if t.dep_ in ('det', 'amod', 'case', 'advmod')], key=lambda x: x.i) token_sintagma.sort(key=lambda x: x.i) return " ".join(t.text for t in token_sintagma) def costruisci_sintagmi_con_dettagli(lista_token): mappa_sintagmi = {} for token in lista_token: if token.dep_ not in ['det', 'case', 'amod', 'punct', 'aux', 'cop', 'mark']: mappa_sintagmi[token.i] = { "text": ottieni_testo_completo(token), "token_details": { "lemma": token.lemma_, "pos": f"{token.pos_}: {spiega_in_italiano(token.pos_, 'pos')}", "tag": f"{token.tag_}: {spiega_in_italiano(token.tag_, 'pos')}", "morph": traduci_morfologia(str(token.morph)) # AGGIORNATO }, "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 = {} if dep == "ROOT": e_nominale = any(c.dep_ == 'cop' for c in token.children) if e_nominale: copula = [c for c in token.children if c.dep_ == 'cop'][0] nome_del_predicato = ottieni_testo_completo(token) risultato_analisi.append({ "text": copula.text, "label_info": {"label": "Copula", "description": "Verbo 'essere' che collega il soggetto alla parte nominale."}, "token_details": { "lemma": copula.lemma_, "pos": f"{copula.pos_}: {spiega_in_italiano(copula.pos_, 'pos')}", "tag": f"{copula.tag_}: {spiega_in_italiano(copula.tag_, 'pos')}", "morph": traduci_morfologia(str(copula.morph)) # AGGIORNATO } }) risultato_analisi.append({ "text": nome_del_predicato, "label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."}, "token_details": sintagma["token_details"] }) else: info_etichetta = MAPPA_DEP.get(dep, {}) elif dep == 'obl': info_etichetta = ottieni_tipo_complemento_con_dettagli(token) elif dep in MAPPA_DEP: info_etichetta = MAPPA_DEP[dep] if info_etichetta: sintagma_da_aggiungere = { "text": sintagma['text'], "label_info": info_etichetta } if sintagma.get("token_details"): sintagma_da_aggiungere["token_details"] = sintagma["token_details"] risultato_analisi.append(sintagma_da_aggiungere) indici_elaborati.add(indice) return risultato_analisi def analizza_proposizione_con_dettagli(token_proposizione): token_nella_proposizione = [t for t in token_proposizione if t.dep_ != 'mark'] return costruisci_sintagmi_con_dettagli(token_nella_proposizione) @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'] doc = nlp(frase) proposizioni_subordinate = [] indici_subordinate = set() for token in doc: if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj"]: 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), "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] analisi_finale = { "full_sentence": frase, "main_clause": { "text": " ".join(t.text for t in token_proposizione_principale if not t.is_punct), "analysis": analizza_proposizione_con_dettagli(token_proposizione_principale) }, "subordinate_clauses": proposizioni_subordinate, "named_entities": entita_nominate } return jsonify(analisi_finale) except Exception as e: print(f"Errore durante l'analisi: {e}") traceback.print_exc() return jsonify({"errore": "Si è verificato un errore interno."}), 500 if __name__ == '__main__': porta = int(os.environ.get("PORT", 8080)) app.run(host="0.0.0.0", port=porta, debug=True)