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'
] |