patopla commited on
Commit
4dcc5d7
·
verified ·
1 Parent(s): 3725ecd

Upload text_analyzer.py

Browse files
Files changed (1) hide show
  1. text_analyzer.py +199 -0
text_analyzer.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, Optional, Union
2
+ import spacy
3
+ from transformers import AutoTokenizer, AutoModel
4
+ import torch
5
+ import numpy as np
6
+ import re
7
+ from patterns import (
8
+ PATRONES_AMBIGUEDAD_LEXICA,
9
+ PATRONES_AMBIGUEDAD_SINTACTICA,
10
+ SUGERENCIAS_MEJORA,
11
+ USER_STORY_PATTERNS
12
+ )
13
+
14
+ class TextAnalyzer:
15
+ """
16
+ Analizador de texto que puede procesar tanto historias de usuario como preguntas generales.
17
+ Integra análisis semántico, detección de ambigüedades y análisis estructural.
18
+ """
19
+
20
+ def __init__(self, model_name: str = "PlanTL-GOB-ES/roberta-base-bne"):
21
+ """
22
+ Inicializa el analizador de texto.
23
+
24
+ Args:
25
+ model_name (str): Nombre del modelo de HuggingFace a utilizar
26
+ """
27
+ try:
28
+ self.nlp = spacy.load("es_core_news_sm")
29
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
30
+ self.model = AutoModel.from_pretrained(model_name)
31
+ except Exception as e:
32
+ raise RuntimeError(f"Error inicializando el analizador: {str(e)}")
33
+
34
+ def _get_embedding(self, texto: str) -> np.ndarray:
35
+ """
36
+ Obtiene el embedding de un texto usando el modelo de transformers.
37
+
38
+ Args:
39
+ texto (str): Texto a procesar
40
+
41
+ Returns:
42
+ np.ndarray: Vector de embedding
43
+ """
44
+ inputs = self.tokenizer(texto, return_tensors="pt", padding=True, truncation=True)
45
+ with torch.no_grad():
46
+ outputs = self.model(**inputs)
47
+ return outputs.last_hidden_state.mean(dim=1).numpy()[0]
48
+
49
+ def calcular_similitud(self, texto1: str, texto2: str) -> float:
50
+ """
51
+ Compara la similitud semántica entre dos textos.
52
+
53
+ Args:
54
+ texto1 (str): Primer texto
55
+ texto2 (str): Segundo texto
56
+
57
+ Returns:
58
+ float: Score de similitud entre 0 y 1
59
+ """
60
+ emb1 = self._get_embedding(texto1)
61
+ emb2 = self._get_embedding(texto2)
62
+ similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
63
+ return float(similarity)
64
+
65
+ def is_user_story(self, text: str) -> bool:
66
+ """
67
+ Determina si el texto es una historia de usuario.
68
+
69
+ Args:
70
+ text (str): Texto a analizar
71
+
72
+ Returns:
73
+ bool: True si es una historia de usuario, False en caso contrario
74
+ """
75
+ # Verificar patrones comunes de historias de usuario
76
+ for pattern in USER_STORY_PATTERNS.values():
77
+ if re.match(pattern, text):
78
+ return True
79
+
80
+ # Verificar palabras clave comunes en historias de usuario
81
+ keywords = ["como", "quiero", "para", "necesito", "debe", "debería"]
82
+ text_lower = text.lower()
83
+ keyword_count = sum(1 for keyword in keywords if keyword in text_lower)
84
+
85
+ return keyword_count >= 2
86
+
87
+ def analyze_user_story(self, text: str) -> Dict:
88
+ """
89
+ Analiza una historia de usuario en busca de ambigüedades.
90
+
91
+ Args:
92
+ text (str): Historia de usuario a analizar
93
+
94
+ Returns:
95
+ Dict: Resultado del análisis con tipos de ambigüedad y sugerencias
96
+ """
97
+ doc = self.nlp(text.strip())
98
+
99
+ # Detectar ambigüedades léxicas
100
+ ambiguedades_lexicas = []
101
+ for patron in PATRONES_AMBIGUEDAD_LEXICA:
102
+ if re.search(patron["patron"], text, re.IGNORECASE):
103
+ ambiguedades_lexicas.append({
104
+ "tipo": patron["tipo"],
105
+ "descripcion": patron["descripcion"]
106
+ })
107
+
108
+ # Detectar ambigüedades sintácticas
109
+ ambiguedades_sintacticas = []
110
+ for patron in PATRONES_AMBIGUEDAD_SINTACTICA:
111
+ if re.search(patron["patron"], text, re.IGNORECASE):
112
+ ambiguedades_sintacticas.append({
113
+ "tipo": patron["tipo"],
114
+ "descripcion": patron["descripcion"]
115
+ })
116
+
117
+ # Generar sugerencias
118
+ sugerencias = []
119
+ if ambiguedades_lexicas or ambiguedades_sintacticas:
120
+ for ambiguedad in ambiguedades_lexicas + ambiguedades_sintacticas:
121
+ tipo = ambiguedad["tipo"]
122
+ if tipo in SUGERENCIAS_MEJORA:
123
+ sugerencias.extend(SUGERENCIAS_MEJORA[tipo])
124
+
125
+ # Calcular score de ambigüedad
126
+ score = len(ambiguedades_lexicas) * 0.4 + len(ambiguedades_sintacticas) * 0.6
127
+ score_normalizado = min(1.0, score / 5.0)
128
+
129
+ return {
130
+ "tipo": "historia_usuario",
131
+ "tiene_ambiguedad": bool(ambiguedades_lexicas or ambiguedades_sintacticas),
132
+ "ambiguedad_lexica": [amb["descripcion"] for amb in ambiguedades_lexicas],
133
+ "ambiguedad_sintactica": [amb["descripcion"] for amb in ambiguedades_sintacticas],
134
+ "sugerencias": sugerencias if sugerencias else ["No se encontraron ambigüedades"],
135
+ "score_ambiguedad": round(score_normalizado, 2)
136
+ }
137
+
138
+ def analyze_general_question(self, text: str) -> Dict:
139
+ """
140
+ Analiza una pregunta general y proporciona una respuesta contextual.
141
+
142
+ Args:
143
+ text (str): Pregunta a analizar
144
+
145
+ Returns:
146
+ Dict: Resultado del análisis con información estructural y contextual
147
+ """
148
+ doc = self.nlp(text.strip())
149
+
150
+ # Identificar el tipo de pregunta
151
+ question_words = {"qué", "cuál", "cómo", "dónde", "cuándo", "por qué", "quién", "cuánto"}
152
+ is_question = any(token.text.lower() in question_words for token in doc)
153
+
154
+ # Extraer entidades nombradas
155
+ entities = [(ent.text, ent.label_) for ent in doc.ents]
156
+
157
+ # Analizar la estructura sintáctica
158
+ root = [token for token in doc if token.dep_ == "ROOT"][0]
159
+ main_verb = root.text if root.pos_ == "VERB" else None
160
+
161
+ # Determinar el contexto de la pregunta
162
+ context = {
163
+ "is_question": is_question,
164
+ "question_type": next((word for word in question_words if word in text.lower()), None),
165
+ "entities": entities,
166
+ "main_verb": main_verb,
167
+ "key_phrases": [chunk.text for chunk in doc.noun_chunks]
168
+ }
169
+
170
+ return {
171
+ "tipo": "pregunta_general",
172
+ "analisis": context,
173
+ "sugerencias": [
174
+ "Esta es una pregunta general que requiere información específica.",
175
+ "Considera usar herramientas de búsqueda o consulta de datos para responderla."
176
+ ]
177
+ }
178
+
179
+ def __call__(self, text: str) -> Dict:
180
+ """
181
+ Procesa el texto y determina si es una historia de usuario o una pregunta general.
182
+
183
+ Args:
184
+ text (str): Texto a analizar
185
+
186
+ Returns:
187
+ Dict: Resultado del análisis según el tipo de texto
188
+ """
189
+ if not text or not isinstance(text, str):
190
+ return {
191
+ "error": "El texto está vacío o no es válido",
192
+ "tipo": "desconocido"
193
+ }
194
+
195
+ # Determinar el tipo de texto y analizarlo
196
+ if self.is_user_story(text):
197
+ return self.analyze_user_story(text)
198
+ else:
199
+ return self.analyze_general_question(text)