analysis_tool / app.py
devusman's picture
update for explaination
31edf0b
raw
history blame
12.4 kB
import os
from flask import Flask, request, jsonify
from flask_cors import CORS
import spacy
import traceback
# --- CORRECTED MODEL LOADING SECTION ---
try:
# Laad het Italiaanse model van spaCy
nlp = spacy.load("it_core_news_sm")
except OSError:
raise RuntimeError(
"Could not find the 'it_core_news_sm' model. "
"Please ensure it is listed and installed from your requirements.txt file."
)
# --- END SECTION ---
# Initialiseer de Flask-app
app = Flask(__name__)
# Schakel Cross-Origin Resource Sharing (CORS) in
CORS(app)
# Een mapping van spaCy dependency-labels naar onze logische analyse-labels met uitleg
DEP_MAP = {
"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 get_complement_type_with_details(token):
"""Verfijnt het complementtype op basis van het voorgaande voorzetsel en geeft details."""
preposition = ""
# Zoek naar een voorzetsel ('case') als een kind van het token
for child in token.children:
if child.dep_ == "case":
preposition = child.text.lower()
break
# Fallback voor sommige structuren waar het voorzetsel een zuster is
if not preposition and token.head.dep_ == 'obl':
for child in token.head.children:
if child.dep_ == "case":
preposition = child.text.lower()
break
if preposition in ["di", "del", "dello", "della", "dei", "degli", "delle"]:
return {"label": "Complemento di Specificazione", "description": "Risponde alla domanda 'di chi?', 'di che cosa?'."}
if preposition in ["a", "al", "allo", "alla", "ai", "agli", "alle"]:
return {"label": "Complemento di Termine", "description": "Risponde alla domanda 'a chi?', 'a che cosa?'."}
if preposition in ["da", "dal", "dallo", "dalla", "dai", "dagli", "dalle"]:
# Controleer op passieve constructie voor Complemento d'Agente
if any(child.dep_ == 'aux:pass' for child 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 preposition 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 preposition 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 preposition 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 preposition in ["per"]:
return {"label": "Complemento di Fine o Causa", "description": "Indica lo scopo o la causa di un'azione."}
if preposition in ["tra", "fra"]:
return {"label": "Complemento di Luogo o Tempo (Partitivo)", "description": "Indica una posizione intermedia o una scelta all'interno di un gruppo."}
# Standaard als geen specifiek voorzetsel wordt gevonden
return {"label": "Complemento Indiretto", "description": "Fornisce un'informazione generica non classificata in modo più specifico."}
def get_full_text(token):
"""Bouwt recursief de volledige tekst van een zinsdeel op, beginnend bij een hoofdtoken."""
# Verzamel het hoofdtoken en de direct gerelateerde modifiers (determiners, adjectieven, voorzetsels)
phrase_tokens = [token] + sorted([t for t in token.children if t.dep_ in ('det', 'amod', 'case', 'advmod')], key=lambda x: x.i)
# Sorteer alle tokens op basis van hun positie in de zin om de juiste volgorde te krijgen
phrase_tokens.sort(key=lambda x: x.i)
return " ".join(t.text for t in phrase_tokens)
def build_phrases_with_details(tokens):
"""Voegt tokens samen tot betekenisvolle grammaticale zinsdelen met gedetailleerde uitleg."""
phrase_map = {}
# Maak een map van belangrijke tokens (hoofden van zinsdelen)
for token in tokens:
# Filter onbelangrijke tokens uit die later worden samengevoegd
if token.dep_ not in ['det', 'case', 'amod', 'punct', 'aux', 'cop', 'mark']:
phrase_map[token.i] = {
"text": get_full_text(token),
# Voeg gedetailleerde grammaticale informatie toe met uitleg
"token_details": {
"lemma": token.lemma_,
"pos": f"{token.pos_}: {spacy.explain(token.pos_)}",
"tag": f"{token.tag_}: {spacy.explain(token.tag_)}",
"morph": str(token.morph) if token.morph else "Non disponibile"
},
"label_info": {},
"token": token
}
analysis_result = []
processed_indices = set()
for index, phrase in phrase_map.items():
if index in processed_indices:
continue
token = phrase['token']
dep = token.dep_
label_info = {}
if dep == "ROOT":
# Controleer op een naamwoordelijk gezegde (bv. "è bello")
is_nominal = any(c.dep_ == 'cop' for c in token.children)
if is_nominal:
copula = [c for c in token.children if c.dep_ == 'cop'][0]
predicate_name = get_full_text(token)
# Voeg de copula apart toe
analysis_result.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_}: {spacy.explain(copula.pos_)}",
"tag": f"{copula.tag_}: {spacy.explain(copula.tag_)}",
"morph": str(copula.morph) if copula.morph else "Non disponibile"
}
})
# Voeg het naamwoordelijk deel van het gezegde toe
analysis_result.append({
"text": predicate_name,
"label_info": {"label": "Parte Nominale del Predicato", "description": "Aggettivo o nome che descrive il soggetto."},
"token_details": phrase["token_details"]
})
else:
# Het is een werkwoordelijk gezegde
label_info = DEP_MAP.get(dep, {})
elif dep == 'obl':
# Gebruik de speciale functie om het type indirect complement te bepalen
label_info = get_complement_type_with_details(token)
elif dep in DEP_MAP:
# Haal het label en de beschrijving op uit de map
label_info = DEP_MAP[dep]
# Voeg het geanalyseerde zinsdeel toe aan de resultatenlijst
if label_info:
phrase_to_add = {
"text": phrase['text'],
"label_info": label_info
}
# Voeg de token-details toe als ze bestaan
if phrase.get("token_details"):
phrase_to_add["token_details"] = phrase["token_details"]
analysis_result.append(phrase_to_add)
processed_indices.add(index)
return analysis_result
def analyze_clause_with_details(clause_tokens):
"""Analyseert een enkele (hoofd- of bij-)zin met details."""
# Verwijder verbindingswoorden (markers) uit de analyse van de zinsdelen zelf
tokens_in_clause = [t for t in clause_tokens if t.dep_ != 'mark']
return build_phrases_with_details(tokens_in_clause)
@app.route("/")
def home():
"""Geeft een eenvoudig welkomstbericht voor de API-root."""
return jsonify({"message": "API for logical analysis is running. Use the /api/analyze endpoint."})
@app.route('/api/analyze', methods=['POST'])
def analyze_sentence():
"""Hoofd-endpoint om een zin te ontvangen en de volledige logische analyse met details terug te sturen."""
try:
data = request.get_json()
if not data or 'sentence' not in data:
return jsonify({"error": "Sentence not provided"}), 400
sentence = data['sentence']
doc = nlp(sentence)
main_clause_tokens = []
subordinate_clauses = []
# Identificeer en scheid bijzinnen
for token in doc:
if token.dep_ in ["acl:relcl", "advcl", "ccomp", "csubj"]:
sub_clause_tokens = list(token.subtree)
sub_clause_type_info = DEP_MAP.get(token.dep_, {"label": "Proposizione Subordinata", "description": "Una frase che dipende da un'altra."})
# Vind het inleidende woord (bv. "che", "quando", "perché")
marker = [child for child in token.children if child.dep_ == 'mark']
intro = marker[0].text if marker else ""
subordinate_clauses.append({
"type_info": sub_clause_type_info,
"text": " ".join(t.text for t in sub_clause_tokens),
"intro": intro,
"analysis": analyze_clause_with_details(sub_clause_tokens)
})
# Bepaal de tokens van de hoofdzin door de tokens van de bijzinnen uit te sluiten
subordinate_indices = {token.i for clause in subordinate_clauses for token in nlp(clause["text"])}
main_clause_tokens = [token for token in doc if token.i not in subordinate_indices]
# Extraheer Named Entities met uitleg
named_entities = [{
"text": ent.text,
"label": ent.label_,
"explanation": spacy.explain(ent.label_) # Zorg voor uitleg
} for ent in doc.ents]
# Stel de uiteindelijke analyse samen
final_analysis = {
"full_sentence": sentence,
"main_clause": {
"text": " ".join(t.text for t in main_clause_tokens if not t.is_punct),
"analysis": analyze_clause_with_details(main_clause_tokens)
},
"subordinate_clauses": subordinate_clauses,
"named_entities": named_entities
}
return jsonify(final_analysis)
except Exception as e:
# Verbeterde foutafhandeling
print(f"Error during analysis: {e}")
traceback.print_exc()
return jsonify({"error": "An internal error occurred."}), 500
if __name__ == '__main__':
# Haal de poort op uit de omgevingsvariabelen voor implementatiegemak
port = int(os.environ.get("PORT", 8080))
app.run(host="0.0.0.0", port=port, debug=True)