Final_Assignment_Template / text_analyzer.py
patopla's picture
Upload text_analyzer.py
4dcc5d7 verified
raw
history blame
7.4 kB
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)