File size: 5,641 Bytes
493ed61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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
)

class SemanticAnalyzer:
    """
    Analizador semántico que utiliza embeddings para comparar textos.
    """
    def __init__(self, model_name: str = "PlanTL-GOB-ES/roberta-base-bne"):
        """
        Inicializa el analizador semántico.
        
        Args:
            model_name (str): Nombre del modelo de HuggingFace a utilizar
        """
        try:
            self.tokenizer = AutoTokenizer.from_pretrained(model_name)
            self.model = AutoModel.from_pretrained(model_name)
        except Exception as e:
            raise RuntimeError(f"Error cargando el modelo {model_name}: {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)

class AmbiguityClassifier:
    """
    Clasificador de ambigüedades en historias de usuario.
    Detecta ambigüedades léxicas y sintácticas, y proporciona sugerencias de mejora.
    """
    
    def __init__(self, model_name: str = "PlanTL-GOB-ES/roberta-base-bne"):
        """
        Inicializa el clasificador de ambigüedades.
        
        Args:
            model_name (str): Nombre del modelo de HuggingFace a utilizar
        """
        try:
            self.nlp = spacy.load("es_core_news_sm")
        except OSError:
            raise RuntimeError("Es necesario instalar el modelo es_core_news_sm. Ejecute: python -m spacy download es_core_news_sm")
            
        self.semantic_analyzer = SemanticAnalyzer(model_name)

    def __call__(self, texto: str) -> Dict[str, Union[bool, List[str], float]]:
        """
        Analiza una historia de usuario en busca de ambigüedades.
        
        Args:
            texto (str): Historia de usuario a analizar
            
        Returns:
            Dict: Resultado del análisis con tipos de ambigüedad y sugerencias
        """
        if not texto or not isinstance(texto, str):
            return {
                "tiene_ambiguedad": False,
                "ambiguedad_lexica": [],
                "ambiguedad_sintactica": [],
                "sugerencias": ["El texto está vacío o no es válido"],
                "score_ambiguedad": 0.0
            }

        # Procesar el texto con spaCy
        doc = self.nlp(texto.strip())
        
        # Detectar ambigüedades léxicas
        ambiguedades_lexicas = []
        for patron in PATRONES_AMBIGUEDAD_LEXICA:
            if re.search(patron["patron"], texto, 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"], texto, re.IGNORECASE):
                ambiguedades_sintacticas.append({
                    "tipo": patron["tipo"],
                    "descripcion": patron["descripcion"]
                })

        # Generar sugerencias de mejora
        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)  # Normalizar a un rango de 0 a 1

        return {
            "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 analizar_similitud_semantica(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
        """
        return self.semantic_analyzer.calcular_similitud(texto1, texto2)