Final_Assignment_Template / text_analyzer.py
patopla's picture
Upload text_analyzer.py
4dcc5d7 verified
from typing import Dict, List, Optional, Union
import spacy
from transformers import AutoTokenizer, AutoModel
import torch
import numpy as np
import re
from patterns import (
PATRONES_AMBIGUEDAD_LEXICA,
PATRONES_AMBIGUEDAD_SINTACTICA,
SUGERENCIAS_MEJORA,
USER_STORY_PATTERNS
)
class TextAnalyzer:
"""
Analizador de texto que puede procesar tanto historias de usuario como preguntas generales.
Integra análisis semántico, detección de ambigüedades y análisis estructural.
"""
def __init__(self, model_name: str = "PlanTL-GOB-ES/roberta-base-bne"):
"""
Inicializa el analizador de texto.
Args:
model_name (str): Nombre del modelo de HuggingFace a utilizar
"""
try:
self.nlp = spacy.load("es_core_news_sm")
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModel.from_pretrained(model_name)
except Exception as e:
raise RuntimeError(f"Error inicializando el analizador: {str(e)}")
def _get_embedding(self, texto: str) -> np.ndarray:
"""
Obtiene el embedding de un texto usando el modelo de transformers.
Args:
texto (str): Texto a procesar
Returns:
np.ndarray: Vector de embedding
"""
inputs = self.tokenizer(texto, return_tensors="pt", padding=True, truncation=True)
with torch.no_grad():
outputs = self.model(**inputs)
return outputs.last_hidden_state.mean(dim=1).numpy()[0]
def calcular_similitud(self, texto1: str, texto2: str) -> float:
"""
Compara la similitud semántica entre dos textos.
Args:
texto1 (str): Primer texto
texto2 (str): Segundo texto
Returns:
float: Score de similitud entre 0 y 1
"""
emb1 = self._get_embedding(texto1)
emb2 = self._get_embedding(texto2)
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
return float(similarity)
def is_user_story(self, text: str) -> bool:
"""
Determina si el texto es una historia de usuario.
Args:
text (str): Texto a analizar
Returns:
bool: True si es una historia de usuario, False en caso contrario
"""
# Verificar patrones comunes de historias de usuario
for pattern in USER_STORY_PATTERNS.values():
if re.match(pattern, text):
return True
# Verificar palabras clave comunes en historias de usuario
keywords = ["como", "quiero", "para", "necesito", "debe", "debería"]
text_lower = text.lower()
keyword_count = sum(1 for keyword in keywords if keyword in text_lower)
return keyword_count >= 2
def analyze_user_story(self, text: str) -> Dict:
"""
Analiza una historia de usuario en busca de ambigüedades.
Args:
text (str): Historia de usuario a analizar
Returns:
Dict: Resultado del análisis con tipos de ambigüedad y sugerencias
"""
doc = self.nlp(text.strip())
# Detectar ambigüedades léxicas
ambiguedades_lexicas = []
for patron in PATRONES_AMBIGUEDAD_LEXICA:
if re.search(patron["patron"], text, re.IGNORECASE):
ambiguedades_lexicas.append({
"tipo": patron["tipo"],
"descripcion": patron["descripcion"]
})
# Detectar ambigüedades sintácticas
ambiguedades_sintacticas = []
for patron in PATRONES_AMBIGUEDAD_SINTACTICA:
if re.search(patron["patron"], text, re.IGNORECASE):
ambiguedades_sintacticas.append({
"tipo": patron["tipo"],
"descripcion": patron["descripcion"]
})
# Generar sugerencias
sugerencias = []
if ambiguedades_lexicas or ambiguedades_sintacticas:
for ambiguedad in ambiguedades_lexicas + ambiguedades_sintacticas:
tipo = ambiguedad["tipo"]
if tipo in SUGERENCIAS_MEJORA:
sugerencias.extend(SUGERENCIAS_MEJORA[tipo])
# Calcular score de ambigüedad
score = len(ambiguedades_lexicas) * 0.4 + len(ambiguedades_sintacticas) * 0.6
score_normalizado = min(1.0, score / 5.0)
return {
"tipo": "historia_usuario",
"tiene_ambiguedad": bool(ambiguedades_lexicas or ambiguedades_sintacticas),
"ambiguedad_lexica": [amb["descripcion"] for amb in ambiguedades_lexicas],
"ambiguedad_sintactica": [amb["descripcion"] for amb in ambiguedades_sintacticas],
"sugerencias": sugerencias if sugerencias else ["No se encontraron ambigüedades"],
"score_ambiguedad": round(score_normalizado, 2)
}
def analyze_general_question(self, text: str) -> Dict:
"""
Analiza una pregunta general y proporciona una respuesta contextual.
Args:
text (str): Pregunta a analizar
Returns:
Dict: Resultado del análisis con información estructural y contextual
"""
doc = self.nlp(text.strip())
# Identificar el tipo de pregunta
question_words = {"qué", "cuál", "cómo", "dónde", "cuándo", "por qué", "quién", "cuánto"}
is_question = any(token.text.lower() in question_words for token in doc)
# Extraer entidades nombradas
entities = [(ent.text, ent.label_) for ent in doc.ents]
# Analizar la estructura sintáctica
root = [token for token in doc if token.dep_ == "ROOT"][0]
main_verb = root.text if root.pos_ == "VERB" else None
# Determinar el contexto de la pregunta
context = {
"is_question": is_question,
"question_type": next((word for word in question_words if word in text.lower()), None),
"entities": entities,
"main_verb": main_verb,
"key_phrases": [chunk.text for chunk in doc.noun_chunks]
}
return {
"tipo": "pregunta_general",
"analisis": context,
"sugerencias": [
"Esta es una pregunta general que requiere información específica.",
"Considera usar herramientas de búsqueda o consulta de datos para responderla."
]
}
def __call__(self, text: str) -> Dict:
"""
Procesa el texto y determina si es una historia de usuario o una pregunta general.
Args:
text (str): Texto a analizar
Returns:
Dict: Resultado del análisis según el tipo de texto
"""
if not text or not isinstance(text, str):
return {
"error": "El texto está vacío o no es válido",
"tipo": "desconocido"
}
# Determinar el tipo de texto y analizarlo
if self.is_user_story(text):
return self.analyze_user_story(text)
else:
return self.analyze_general_question(text)