File size: 12,503 Bytes
42693f7
 
 
 
31edf0b
42693f7
96e4672
42693f7
96e4672
870c988
 
 
96e4672
 
870c988
96e4672
42693f7
96e4672
42693f7
4f5a1e9
96e4672
42693f7
 
e90e953
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96e4672
 
31edf0b
 
 
 
 
 
 
 
 
 
 
 
 
42693f7
 
96e4672
 
 
 
 
 
42693f7
96e4672
 
 
 
4f5a1e9
42693f7
96e4672
31edf0b
96e4672
31edf0b
96e4672
 
31edf0b
 
96e4672
31edf0b
96e4672
31edf0b
96e4672
31edf0b
96e4672
31edf0b
96e4672
31edf0b
 
 
42693f7
96e4672
 
 
 
 
 
 
 
 
4f5a1e9
96e4672
4f5a1e9
96e4672
 
31edf0b
 
e90e953
 
31edf0b
 
 
4f5a1e9
 
 
96e4672
 
4f5a1e9
e90e953
96e4672
4f5a1e9
 
96e4672
4f5a1e9
96e4672
4f5a1e9
 
96e4672
 
4f5a1e9
96e4672
 
4f5a1e9
31edf0b
 
 
e90e953
 
31edf0b
 
4f5a1e9
96e4672
 
31edf0b
96e4672
4f5a1e9
 
96e4672
4f5a1e9
96e4672
 
 
4f5a1e9
96e4672
 
 
 
31edf0b
96e4672
 
 
31edf0b
96e4672
4f5a1e9
96e4672
4f5a1e9
96e4672
 
 
 
4f5a1e9
42693f7
 
96e4672
 
42693f7
 
96e4672
 
42693f7
96e4672
 
 
42693f7
96e4672
 
42693f7
96e4672
e90e953
4f5a1e9
42693f7
4f5a1e9
96e4672
e90e953
 
 
96e4672
4f5a1e9
96e4672
 
4f5a1e9
96e4672
 
e90e953
4f5a1e9
96e4672
4f5a1e9
 
96e4672
4f5a1e9
96e4672
31edf0b
 
e90e953
31edf0b
 
96e4672
 
4f5a1e9
96e4672
 
4f5a1e9
96e4672
 
4f5a1e9
42693f7
96e4672
42693f7
 
96e4672
4f5a1e9
96e4672
4f5a1e9
 
96e4672
 
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
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__)

# Abilita la Condivisione delle Risorse tra Origini Diverse (CORS)
CORS(app)

# --- INIZIO SEZIONE TRADUZIONI ---
# Mappe per le traduzioni in italiano delle spiegazioni di spaCy
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)."
}

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)
    # Ritorna il tag originale se non trova una spiegazione
    return tag
# --- FINE SEZIONE TRADUZIONI ---

# Mappatura delle etichette di dipendenza di spaCy alle nostre etichette di analisi logica con spiegazioni
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):
    """Affina il tipo di complemento basandosi sulla preposizione precedente e fornisce dettagli."""
    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):
    """Costruisce ricorsivamente il testo completo di un sintagma, partendo da un token principale."""
    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):
    """Aggrega i token in sintagmi grammaticali significativi con spiegazioni dettagliate."""
    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')}", # Usa 'pos' per la spiegazione del tag più generica
                    "morph": str(token.morph) if token.morph else "Non disponibile"
                },
                "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": str(copula.morph) if copula.morph else "Non disponibile"
                    }
                })
                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):
    """Analizza una singola proposizione (principale o subordinata) con dettagli."""
    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():
    """Restituisce un semplice messaggio di benvenuto per la radice dell'API."""
    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():
    """Endpoint principale per ricevere una frase e restituire l'analisi logica completa con dettagli."""
    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)