# modules/database/semantic_mongo_live_db.py import logging from datetime import datetime, timezone import base64 from bson import Binary from pymongo.errors import PyMongoError # Configuración del logger logger = logging.getLogger(__name__) COLLECTION_NAME = 'student_semantic_live_analysis' def store_student_semantic_live_result(username, text, analysis_result, lang_code='en'): """ Guarda el resultado del análisis semántico en vivo en MongoDB. Versión mejorada con manejo robusto de errores y verificación de datos. """ try: # 1. Validación exhaustiva de los parámetros de entrada if not username or not isinstance(username, str): logger.error("Nombre de usuario inválido o vacío") return False if not text or not isinstance(text, str): logger.error("Texto de análisis inválido o vacío") return False if not analysis_result or not isinstance(analysis_result, dict): logger.error("Resultado de análisis inválido o vacío") return False # 2. Preparación del gráfico conceptual con múltiples formatos soportados concept_graph_data = None if 'concept_graph' in analysis_result and analysis_result['concept_graph'] is not None: try: graph_data = analysis_result['concept_graph'] if isinstance(graph_data, bytes): # Codificar a base64 para almacenamiento eficiente concept_graph_data = base64.b64encode(graph_data).decode('utf-8') elif isinstance(graph_data, str): # Si ya es string (base64), usarlo directamente concept_graph_data = graph_data elif isinstance(graph_data, Binary): # Si es Binary de pymongo, convertirlo concept_graph_data = base64.b64encode(graph_data).decode('utf-8') else: logger.warning(f"Formato de gráfico no soportado: {type(graph_data)}") except Exception as e: logger.error(f"Error al procesar gráfico conceptual: {str(e)}", exc_info=True) # Continuar sin gráfico en lugar de fallar completamente # 3. Preparación del documento con validación de campos analysis_document = { 'username': username, 'timestamp': datetime.now(timezone.utc), 'text': text[:10000], # Limitar tamaño para prevenir documentos muy grandes 'analysis_type': 'semantic_live', 'language': lang_code, 'metadata': { 'version': '1.0', 'source': 'live_interface' } } # Campos opcionales con validación if 'key_concepts' in analysis_result and isinstance(analysis_result['key_concepts'], list): analysis_document['key_concepts'] = analysis_result['key_concepts'][:50] # Limitar a 50 conceptos if 'concept_centrality' in analysis_result and isinstance(analysis_result['concept_centrality'], dict): analysis_document['concept_centrality'] = analysis_result['concept_centrality'] if concept_graph_data: analysis_document['concept_graph'] = concept_graph_data # 4. Operación de base de datos con manejo de errores específico try: collection = get_collection(COLLECTION_NAME) if not collection: logger.error("No se pudo obtener la colección MongoDB") return False result = collection.insert_one(analysis_document) if result.inserted_id: logger.info(f"Análisis guardado exitosamente para {username}. ID: {result.inserted_id}") return True logger.error("La operación de inserción no devolvió un ID") return False except PyMongoError as mongo_error: logger.error(f"Error de MongoDB al guardar análisis: {str(mongo_error)}", exc_info=True) return False except Exception as e: logger.error(f"Error inesperado al guardar análisis: {str(e)}", exc_info=True) return False def get_student_semantic_live_analysis(username, limit=10): """ Recupera los análisis semánticos en vivo de un estudiante. Versión mejorada con paginación y manejo de errores. """ try: # Validación de parámetros if not username or not isinstance(username, str): logger.error("Nombre de usuario inválido para recuperación") return [] if not isinstance(limit, int) or limit <= 0: limit = 10 # Valor por defecto si el límite es inválido # Consulta con proyección para optimizar transferencia query = { "username": username, "analysis_type": "semantic_live" } projection = { "timestamp": 1, "text": {"$substr": ["$text", 0, 200]}, # Solo primeros 200 caracteres "key_concepts": {"$slice": ["$key_concepts", 10]}, # Solo primeros 10 conceptos "concept_graph": 1, "_id": 1, "metadata": 1 } # Operación de base de datos con manejo de errores try: collection = get_collection(COLLECTION_NAME) if not collection: logger.error("No se pudo obtener la colección MongoDB") return [] cursor = collection.find(query, projection).sort("timestamp", -1).limit(limit) results = list(cursor) # Post-procesamiento para asegurar formato consistente for doc in results: if 'concept_graph' in doc and isinstance(doc['concept_graph'], str): try: # Convertir base64 string a bytes para compatibilidad doc['concept_graph'] = base64.b64decode(doc['concept_graph']) except Exception as e: logger.warning(f"Error al decodificar gráfico: {str(e)}") doc.pop('concept_graph', None) logger.info(f"Recuperados {len(results)} análisis para {username}") return results except PyMongoError as mongo_error: logger.error(f"Error de MongoDB al recuperar análisis: {str(mongo_error)}") return [] except Exception as e: logger.error(f"Error inesperado al recuperar análisis: {str(e)}", exc_info=True) return [] __all__ = [ 'store_student_semantic_live_result', 'get_student_semantic_live_analysis' ]