File size: 8,536 Bytes
5a91987
 
92df942
5a91987
 
 
 
92df942
5a91987
 
 
92df942
5a91987
 
 
 
 
 
 
 
 
 
 
92df942
5a91987
 
 
 
92df942
 
5a91987
 
92df942
 
5a91987
 
 
92df942
 
 
 
 
 
 
 
 
 
5a91987
92df942
 
 
5a91987
92df942
 
 
 
 
 
 
 
 
 
 
 
 
5a91987
92df942
 
 
 
5a91987
92df942
5a91987
 
92df942
 
 
 
5a91987
92df942
 
5a91987
 
92df942
 
 
 
 
 
 
 
 
 
5a91987
 
92df942
5a91987
 
 
9bab4e3
5a91987
 
9bab4e3
5a91987
 
 
 
 
 
 
 
eaa5034
a3a54d6
eaa5034
5a91987
9bab4e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3a54d6
9bab4e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a91987
9bab4e3
22a1691
 
5a91987
22a1691
9bab4e3
22a1691
5a91987
9bab4e3
5a91987
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
efccf51
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#/modules/database/semantic_mongo_db.py

# Importaciones estándar corregidas y optimizadas
import io
import base64
from datetime import datetime, timezone
import logging
from PIL import Image  # Necesario para la optimización

# Importaciones de terceros
import matplotlib.pyplot as plt
from pymongo.errors import PyMongoError

# Importaciones locales
from .mongo_db import (
    get_collection,
    insert_document, 
    find_documents, 
    update_document, 
    delete_document
)

# Configuración del logger
logger = logging.getLogger(__name__)
COLLECTION_NAME = 'student_semantic_analysis'

def store_student_semantic_result(username, text, analysis_result, lang_code='en'):
    """
    NOMBRE PRESERVADO. Guarda el resultado del análisis semántico en MongoDB.
    Optimizado para evitar el error 413 (Request size too large) mediante compresión.
    """
    try:
        # 1. Validación de datos mínimos
        if not all([username, text, analysis_result]):
            logger.error("Datos insuficientes para guardar el análisis")
            return False

        collection = get_collection(COLLECTION_NAME)
        if collection is None:
            logger.error(f"No se pudo obtener la colección {COLLECTION_NAME}")
            return False

        # 2. Procesamiento Único del Gráfico (Optimizado y sin redundancias)
        concept_graph_data = analysis_result.get('concept_graph')
        final_graph_bytes = None

        if concept_graph_data:
            try:
                # Convertir de base64 a bytes si es necesario
                if isinstance(concept_graph_data, str):
                    final_graph_bytes = base64.b64decode(concept_graph_data)
                else:
                    final_graph_bytes = concept_graph_data

                # --- COMPRESIÓN PARA EVITAR ERROR 413 ---
                img = Image.open(io.BytesIO(final_graph_bytes))
                if img.mode != 'RGB':
                    img = img.convert('RGB')
                
                output = io.BytesIO()
                # Calidad 75% reduce el peso drásticamente sin perder claridad en los nodos
                img.save(output, format="JPEG", quality=75, optimize=True)
                final_graph_bytes = output.getvalue()
                
                logger.info(f"Grafo optimizado: {len(final_graph_bytes)} bytes para usuario {username}")
            except Exception as e:
                logger.warning(f"Error procesando gráfico, se intentará guardar original: {str(e)}")
                # Si falla la compresión, mantenemos los bytes originales si existen
                if not final_graph_bytes and isinstance(concept_graph_data, bytes):
                    final_graph_bytes = concept_graph_data

        # 3. Preparar el documento (Estructura limpia)
        analysis_document = {
            'username': username,
            'timestamp': datetime.now(timezone.utc),
            'text': text[:50000],  # Límite de seguridad para textos extremadamente largos
            'language': lang_code,
            'analysis_type': 'standard_semantic',
            'key_concepts': analysis_result.get('key_concepts', []),
            'entities': analysis_result.get('entities', []),
            'concept_graph': final_graph_bytes  # Los bytes ya procesados/comprimidos
        }

        # 4. Inserción en MongoDB
        try:
            result = collection.insert_one(analysis_document)
            if result.inserted_id:
                logger.info(f"Análisis guardado exitosamente. ID: {result.inserted_id}")
                return True
            return False
        except PyMongoError as e:
            logger.error(f"Error de inserción en MongoDB: {str(e)}")
            return False

    except Exception as e:
        logger.error(f"Error inesperado en store_student_semantic_result: {str(e)}", exc_info=True)
        return False

####################################################################################
def get_student_semantic_analysis(username, limit=10):
    """
    Recupera los análisis semánticos de un estudiante.
    Ordena correctamente resolviendo discrepancias de formato de fecha.
    """
    try:
        collection = get_collection(COLLECTION_NAME)
        if collection is None:
            logger.error("No se pudo obtener la colección semantic")
            return []

        query = {
            "username": username,
            "concept_graph": {"$exists": True, "$ne": None}
        }
        
        # Pipeline de agregación para unificar el tipo de dato antes de ordenar
        pipeline = [
            {"$match": query},
            {"$addFields": {
                "normalized_date": {
                    "$convert": {
                        "input": "$timestamp",
                        "to": "date",
                        "onError": None, # Si hay un formato inválido, no rompe la consulta
                        "onNull": None
                    }
                }
            }},
            # Ordenamos usando la fecha ya normalizada
            {"$sort": {"normalized_date": -1}}
        ]
        
        # Aplicamos el límite solo si se especifica (útil para get_student_semantic_data)
        if limit is not None:
            pipeline.append({"$limit": limit})
            
        # Proyectamos solo los campos necesarios
        pipeline.append({
            "$project": {
                "timestamp": 1,
                "text": 1,
                "key_concepts": 1,
                "concept_graph": 1,
                "analysis_type": 1,
                "_id": 1
            }
        })
        
        results = list(collection.aggregate(pipeline))
        logger.info(f"Recuperados {len(results)} análisis para {username}")
        return results
        
    except PyMongoError as e:
        logger.error(f"Error de MongoDB en la agregación: {str(e)}")
        return []
    except Exception as e:
        logger.error(f"Error inesperado al recuperar análisis: {str(e)}")
        return []
####################################################################################################


def update_student_semantic_analysis(analysis_id, update_data):
    """
    Actualiza un análisis semántico existente.
    Args:
        analysis_id: ID del análisis a actualizar
        update_data: Datos a actualizar
    """
    query = {"_id": analysis_id}
    update = {"$set": update_data}
    return update_document(COLLECTION_NAME, query, update)

def delete_student_semantic_analysis(analysis_id):
    """
    Elimina un análisis semántico.
    Args:
        analysis_id: ID del análisis a eliminar
    """
    query = {"_id": analysis_id}
    return delete_document(COLLECTION_NAME, query)


############################################################################
def get_student_semantic_data(username):
    """
    Obtiene todos los análisis semánticos de un estudiante.
    Args:
        username: Nombre del usuario
    Returns:
        dict: Diccionario con todos los análisis del estudiante
    """
    try:
        analyses = get_student_semantic_analysis(username, limit=None)
        
        formatted_analyses = []
        for analysis in analyses:
            # Asegurarse de que los campos existan antes de acceder a ellos
            formatted_analysis = {
                'timestamp': analysis.get('timestamp'),
                'text': analysis.get('text', ''),  # Usar get() con valor por defecto
                'key_concepts': analysis.get('key_concepts', []),
                'entities': analysis.get('entities', []),
                'concept_graph': analysis.get('concept_graph')  # Mantener el gráfico
            }
            formatted_analyses.append(formatted_analysis)
        
        return {
            'username': username,
            'entries': formatted_analyses,
            'count': len(formatted_analyses),
            'status': 'success'
        }
    
    except Exception as e:
        logger.error(f"Error al obtener datos semánticos para {username}: {str(e)}")
        return {
            'username': username,
            'entries': [],
            'count': 0,
            'status': 'error',
            'error': str(e)
        }


##########################################################################
# Exportar las funciones necesarias
__all__ = [
    'store_student_semantic_result',
    'get_student_semantic_analysis',
    'update_student_semantic_analysis',
    'delete_student_semantic_analysis',
    'get_student_semantic_data'
]