Spaces:
Sleeping
Sleeping
| import os | |
| from flask import Flask, request, jsonify | |
| from flask_cors import CORS | |
| import spacy | |
| import traceback | |
| # --- SEZIONE CARICAMENTO MODELLO --- | |
| try: | |
| # Carica un modello più accurato per l'italiano di spaCy (usa 'lg' per maggiore precisione) | |
| 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 dal tuo file requirements.txt. " | |
| "Puoi scaricarlo con: python -m spacy download it_core_news_lg" | |
| ) | |
| # --- 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)." | |
| } | |
| # Dizionario per le traduzioni morfologiche (espanso per coprire più casi) | |
| 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", | |
| "Case": "Caso", "Poss": "Possessivo", "Reflex": "Riflessivo", | |
| "Aspect": "Aspetto", "Voice": "Voce", | |
| # 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", "Pqp": "Trapassato", | |
| "1": "1ª", "2": "2ª", "3": "3ª", | |
| "Fin": "Finita", "Inf": "Infinito", "Part": "Participio", "Ger": "Gerundio", | |
| "Prs": "Personale", "Rel": "Relativo", "Int": "Interrogativo", "Dem": "Dimostrativo", "Art": "Articolativo", "Ind": "Indefinito", | |
| "Yes": "Sì", "No": "No", | |
| "Ind": "Indeterminato", "Def": "Determinato", | |
| "Abs": "Assoluto", "Cmp": "Comparativo", "Sup": "Superlativo", | |
| "Nom": "Nominativo", "Acc": "Accusativo", "Gen": "Genitivo", "Dat": "Dativo", | |
| "Perf": "Perfetto", "Prog": "Progressivo", | |
| "Act": "Attiva", "Pass": "Passiva", | |
| } | |
| def spiega_in_italiano(tag, tipo='pos'): | |
| """Fornisce una spiegazione in italiano per un tag POS o di entità.""" | |
| if tipo == 'pos': | |
| # Per i tag dettagliati, estrai la parte POS principale se necessario | |
| pos_base = tag.split("__")[0] if "__" in tag else tag | |
| return SPIEGAZIONI_POS_IT.get(pos_base, 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 or 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: | |
| parti_tradotte.append(TRADUZIONI_MORFOLOGIA.get(parte, parte)) | |
| return ", ".join(sorted(set(parti_tradotte))) or "Non disponibile" # Sort and unique for cleanliness | |
| # --- FINE SEZIONE TRADUZIONI --- | |
| 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."} | |
| } | |
| def ottieni_tipo_complemento_con_dettagli(token): | |
| preposizione = "" | |
| # Cerca la preposizione nei figli o nel contesto | |
| 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 a complementi (espansa per più varianti e casi) | |
| mappa_preposizioni = { | |
| "di": "Complemento di Specificazione", | |
| "del": "Complemento di Specificazione", | |
| "dello": "Complemento di Specificazione", | |
| "della": "Complemento di Specificazione", | |
| "dei": "Complemento di Specificazione", | |
| "degli": "Complemento di Specificazione", | |
| "delle": "Complemento di Specificazione", | |
| "a": "Complemento di Termine", | |
| "al": "Complemento di Termine", | |
| "allo": "Complemento di Termine", | |
| "alla": "Complemento di Termine", | |
| "ai": "Complemento di Termine", | |
| "agli": "Complemento di Termine", | |
| "alle": "Complemento di Termine", | |
| "da": "Complemento di Moto da Luogo", | |
| "dal": "Complemento di Moto da Luogo", | |
| "dallo": "Complemento di Moto da Luogo", | |
| "dalla": "Complemento di Moto da Luogo", | |
| "dai": "Complemento di Moto da Luogo", | |
| "dagli": "Complemento di Moto da Luogo", | |
| "dalle": "Complemento di Moto da Luogo", | |
| "in": "Complemento di Stato in Luogo", | |
| "nel": "Complemento di Stato in Luogo", | |
| "nello": "Complemento di Stato in Luogo", | |
| "nella": "Complemento di Stato in Luogo", | |
| "nei": "Complemento di Stato in Luogo", | |
| "negli": "Complemento di Stato in Luogo", | |
| "nelle": "Complemento di Stato in Luogo", | |
| "con": "Complemento di Compagnia o Mezzo", | |
| "col": "Complemento di Compagnia o Mezzo", | |
| "coi": "Complemento di Compagnia o Mezzo", | |
| "su": "Complemento di Argomento o Luogo", | |
| "sul": "Complemento di Argomento o Luogo", | |
| "sullo": "Complemento di Argomento o Luogo", | |
| "sulla": "Complemento di Argomento o Luogo", | |
| "sui": "Complemento di Argomento o Luogo", | |
| "sugli": "Complemento di Argomento o Luogo", | |
| "sulle": "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_ == 'aux:pass' 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): | |
| # Raccogli il sintagma completo ricorsivamente per includere modificatori nidificati | |
| def raccogli_figli(t): | |
| figli = list(t.children) | |
| for f in figli: | |
| if f.dep_ in ('det', 'amod', 'case', 'advmod', 'nmod', 'appos', 'acl', 'compound', 'flat'): | |
| figli.extend(raccogli_figli(f)) | |
| return figli | |
| token_sintagma = [token] + raccogli_figli(token) | |
| token_sintagma = sorted(set(token_sintagma), key=lambda x: x.i) # Ordina e rimuovi duplicati | |
| return " ".join(t.text for t in token_sintagma if not t.is_punct).strip() | |
| 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', 'cc', 'advmod', 'aux:pass']: # Espanso per skipping più elementi ausiliari | |
| 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)) | |
| }, | |
| "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."}) | |
| if dep == "ROOT": | |
| # Gestione predicato nominale migliorata | |
| copula_children = [c for c in token.children if c.dep_ == 'cop'] | |
| e_nominale = bool(copula_children) | |
| if e_nominale: | |
| copula = copula_children[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)) | |
| } | |
| }) | |
| 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"] | |
| }) | |
| indici_elaborati.add(indice) | |
| continue | |
| else: | |
| info_etichetta = MAPPA_DEP.get(dep, {}) | |
| elif dep in ('obl', 'obl:agent'): | |
| info_etichetta = ottieni_tipo_complemento_con_dettagli(token) | |
| elif dep == 'nsubj:pass': | |
| info_etichetta = MAPPA_DEP.get('nsubj:pass', MAPPA_DEP['nsubj']) | |
| 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) | |
| # Rimuovi duplicati dal risultato finale | |
| 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 t.dep_ != 'mark' and not t.is_punct and not t.is_space] | |
| return costruisci_sintagmi_con_dettagli(token_nella_proposizione) | |
| def home(): | |
| return jsonify({"messaggio": "L'API per l'analisi logica è in esecuzione. Usa l'endpoint /api/analyze."}) | |
| 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"]: # Espanso per più tipi, inclusa parataxis per coordinate | |
| 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: | |
| print(f"Errore durante l'analisi: {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) # Threaded per gestire più richieste |