AIdeaText commited on
Commit
bc146d8
·
verified ·
1 Parent(s): a673f38

Upload 15 files

Browse files
modules/database/__init__.py ADDED
File without changes
modules/database/chat_mongo_db.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /modules/database/chat_mongo_db.py
2
+ from .mongo_db import insert_document, find_documents, get_collection
3
+ from datetime import datetime, timezone
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+ COLLECTION_NAME = 'chat_history-v3'
8
+
9
+ def get_chat_history(username: str, analysis_type: str = 'sidebar', limit: int = None) -> list:
10
+ """
11
+ Recupera el historial del chat.
12
+
13
+ Args:
14
+ username: Nombre del usuario
15
+ analysis_type: Tipo de análisis ('sidebar' por defecto)
16
+ limit: Límite de conversaciones a recuperar
17
+
18
+ Returns:
19
+ list: Lista de conversaciones con formato
20
+ """
21
+ try:
22
+ query = {
23
+ "username": username,
24
+ "analysis_type": analysis_type
25
+ }
26
+
27
+ collection = get_collection(COLLECTION_NAME)
28
+ if collection is None:
29
+ logger.error("No se pudo obtener la colección de chat")
30
+ return []
31
+
32
+ # Obtener y formatear conversaciones
33
+ cursor = collection.find(query).sort("timestamp", -1)
34
+ if limit:
35
+ cursor = cursor.limit(limit)
36
+
37
+ conversations = []
38
+ for chat in cursor:
39
+ try:
40
+ formatted_chat = {
41
+ 'timestamp': chat['timestamp'],
42
+ 'messages': [
43
+ {
44
+ 'role': msg.get('role', 'unknown'),
45
+ 'content': msg.get('content', '')
46
+ }
47
+ for msg in chat.get('messages', [])
48
+ ]
49
+ }
50
+ conversations.append(formatted_chat)
51
+ except Exception as e:
52
+ logger.error(f"Error formateando chat: {str(e)}")
53
+ continue
54
+
55
+ return conversations
56
+
57
+ except Exception as e:
58
+ logger.error(f"Error al recuperar historial de chat: {str(e)}")
59
+ return []
60
+
61
+ def store_chat_history(username: str, messages: list, analysis_type: str = 'sidebar') -> bool:
62
+ """
63
+ Guarda el historial del chat.
64
+
65
+ Args:
66
+ username: Nombre del usuario
67
+ messages: Lista de mensajes a guardar
68
+ analysis_type: Tipo de análisis
69
+
70
+ Returns:
71
+ bool: True si se guardó correctamente
72
+ """
73
+ try:
74
+ collection = get_collection(COLLECTION_NAME)
75
+ if collection is None:
76
+ logger.error("No se pudo obtener la colección de chat")
77
+ return False
78
+
79
+ # Formatear mensajes antes de guardar
80
+ formatted_messages = [
81
+ {
82
+ 'role': msg.get('role', 'unknown'),
83
+ 'content': msg.get('content', ''),
84
+ 'timestamp': datetime.now(timezone.utc).isoformat()
85
+ }
86
+ for msg in messages
87
+ ]
88
+
89
+ chat_document = {
90
+ 'username': username,
91
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
92
+ 'messages': formatted_messages,
93
+ 'analysis_type': analysis_type
94
+ }
95
+
96
+ result = collection.insert_one(chat_document)
97
+ if result.inserted_id:
98
+ logger.info(f"Historial de chat guardado con ID: {result.inserted_id} para el usuario: {username}")
99
+ return True
100
+
101
+ logger.error("No se pudo insertar el documento")
102
+ return False
103
+
104
+ except Exception as e:
105
+ logger.error(f"Error al guardar historial de chat: {str(e)}")
106
+ return False
107
+
108
+
109
+ #def get_chat_history(username, analysis_type=None, limit=10):
110
+ # query = {"username": username}
111
+ # if analysis_type:
112
+ # query["analysis_type"] = analysis_type
113
+
114
+ # return find_documents(COLLECTION_NAME, query, sort=[("timestamp", -1)], limit=limit)
115
+
116
+ # Agregar funciones para actualizar y eliminar chat si es necesario
modules/database/claude_recommendations_mongo_db.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/database/claude_recommendations_mongo_db.py
2
+ from datetime import datetime, timezone, timedelta
3
+ import logging
4
+ from .mongo_db import get_collection
5
+
6
+ logger = logging.getLogger(__name__)
7
+ COLLECTION_NAME = 'student_claude_recommendations'
8
+
9
+ def store_claude_recommendation(username, text, metrics, text_type, recommendations):
10
+ """
11
+ Guarda las recomendaciones generadas por Claude AI.
12
+
13
+ Args:
14
+ username: Nombre del usuario
15
+ text: Texto analizado
16
+ metrics: Métricas del análisis
17
+ text_type: Tipo de texto (academic_article, university_work, general_communication)
18
+ recommendations: Recomendaciones generadas por Claude
19
+
20
+ Returns:
21
+ bool: True si se guardó correctamente, False en caso contrario
22
+ """
23
+ try:
24
+ # Verificar parámetros
25
+ if not all([username, text, recommendations]):
26
+ logger.error("Faltan parámetros requeridos para guardar recomendaciones de Claude")
27
+ return False
28
+
29
+ collection = get_collection(COLLECTION_NAME)
30
+ if collection is None:
31
+ logger.error("No se pudo obtener la colección de recomendaciones de Claude")
32
+ return False
33
+
34
+ # Crear documento
35
+ document = {
36
+ 'username': username,
37
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
38
+ 'text': text,
39
+ 'metrics': metrics or {},
40
+ 'text_type': text_type,
41
+ 'recommendations': recommendations,
42
+ 'analysis_type': 'claude_recommendation'
43
+ }
44
+
45
+ # Insertar documento
46
+ result = collection.insert_one(document)
47
+ if result.inserted_id:
48
+ logger.info(f"""
49
+ Recomendaciones de Claude guardadas:
50
+ - Usuario: {username}
51
+ - ID: {result.inserted_id}
52
+ - Tipo de texto: {text_type}
53
+ - Longitud del texto: {len(text)}
54
+ """)
55
+
56
+ # Verificar almacenamiento
57
+ storage_verified = verify_recommendation_storage(username)
58
+ if not storage_verified:
59
+ logger.warning("Verificación de almacenamiento de recomendaciones falló")
60
+
61
+ return True
62
+
63
+ logger.error("No se pudo insertar el documento de recomendaciones")
64
+ return False
65
+
66
+ except Exception as e:
67
+ logger.error(f"Error guardando recomendaciones de Claude: {str(e)}")
68
+ return False
69
+
70
+ def verify_recommendation_storage(username):
71
+ """
72
+ Verifica que las recomendaciones se están guardando correctamente.
73
+
74
+ Args:
75
+ username: Nombre del usuario
76
+
77
+ Returns:
78
+ bool: True si la verificación es exitosa, False en caso contrario
79
+ """
80
+ try:
81
+ collection = get_collection(COLLECTION_NAME)
82
+ if collection is None:
83
+ logger.error("No se pudo obtener la colección para verificación de recomendaciones")
84
+ return False
85
+
86
+ # Buscar documentos recientes del usuario
87
+ timestamp_threshold = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat()
88
+ recent_docs = collection.find({
89
+ 'username': username,
90
+ 'timestamp': {'$gte': timestamp_threshold}
91
+ }).sort('timestamp', -1).limit(1)
92
+
93
+ docs = list(recent_docs)
94
+ if docs:
95
+ logger.info(f"""
96
+ Último documento de recomendaciones guardado:
97
+ - ID: {docs[0]['_id']}
98
+ - Timestamp: {docs[0]['timestamp']}
99
+ - Tipo de texto: {docs[0].get('text_type', 'N/A')}
100
+ """)
101
+ return True
102
+
103
+ logger.warning(f"No se encontraron documentos recientes de recomendaciones para {username}")
104
+ return False
105
+
106
+ except Exception as e:
107
+ logger.error(f"Error verificando almacenamiento de recomendaciones: {str(e)}")
108
+ return False
109
+
110
+ def get_claude_recommendations(username, limit=10):
111
+ """
112
+ Obtiene las recomendaciones más recientes de Claude para un usuario.
113
+
114
+ Args:
115
+ username: Nombre del usuario
116
+ limit: Número máximo de recomendaciones a recuperar
117
+
118
+ Returns:
119
+ list: Lista de recomendaciones
120
+ """
121
+ try:
122
+ collection = get_collection(COLLECTION_NAME)
123
+ if collection is None:
124
+ logger.error("No se pudo obtener la colección de recomendaciones")
125
+ return []
126
+
127
+ results = collection.find(
128
+ {'username': username}
129
+ ).sort('timestamp', -1).limit(limit)
130
+
131
+ recommendations = list(results)
132
+ logger.info(f"Recuperadas {len(recommendations)} recomendaciones de Claude para {username}")
133
+ return recommendations
134
+
135
+ except Exception as e:
136
+ logger.error(f"Error obteniendo recomendaciones de Claude: {str(e)}")
137
+ return []
modules/database/current_situation_mongo_db.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/database/current_situation_mongo_db.py
2
+ from datetime import datetime, timezone, timedelta
3
+ import logging
4
+ from .mongo_db import get_collection
5
+
6
+ logger = logging.getLogger(__name__)
7
+ COLLECTION_NAME = 'student_current_situation'
8
+
9
+ # En modules/database/current_situation_mongo_db.py
10
+
11
+ def store_current_situation_result(username, text, metrics, feedback):
12
+ """
13
+ Guarda los resultados del análisis de situación actual.
14
+ """
15
+ try:
16
+ # Verificar parámetros
17
+ if not all([username, text, metrics]):
18
+ logger.error("Faltan parámetros requeridos")
19
+ return False
20
+
21
+ collection = get_collection(COLLECTION_NAME)
22
+ if collection is None:
23
+ logger.error("No se pudo obtener la colección")
24
+ return False
25
+
26
+ # Crear documento
27
+ document = {
28
+ 'username': username,
29
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
30
+ 'text': text,
31
+ 'metrics': metrics,
32
+ 'feedback': feedback or {},
33
+ 'analysis_type': 'current_situation'
34
+ }
35
+
36
+ # Insertar documento y verificar
37
+ result = collection.insert_one(document)
38
+ if result.inserted_id:
39
+ logger.info(f"""
40
+ Análisis de situación actual guardado:
41
+ - Usuario: {username}
42
+ - ID: {result.inserted_id}
43
+ - Longitud texto: {len(text)}
44
+ """)
45
+
46
+ # Verificar almacenamiento
47
+ storage_verified = verify_storage(username)
48
+ if not storage_verified:
49
+ logger.warning("Verificación de almacenamiento falló")
50
+
51
+ return True
52
+
53
+ logger.error("No se pudo insertar el documento")
54
+ return False
55
+
56
+ except Exception as e:
57
+ logger.error(f"Error guardando análisis de situación actual: {str(e)}")
58
+ return False
59
+
60
+ def verify_storage(username):
61
+ """
62
+ Verifica que los datos se están guardando correctamente.
63
+ """
64
+ try:
65
+ collection = get_collection(COLLECTION_NAME)
66
+ if collection is None:
67
+ logger.error("No se pudo obtener la colección para verificación")
68
+ return False
69
+
70
+ # Buscar documentos recientes del usuario
71
+ timestamp_threshold = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat()
72
+
73
+ recent_docs = collection.find({
74
+ 'username': username,
75
+ 'timestamp': {'$gte': timestamp_threshold}
76
+ }).sort('timestamp', -1).limit(1)
77
+
78
+ docs = list(recent_docs)
79
+ if docs:
80
+ logger.info(f"""
81
+ Último documento guardado:
82
+ - ID: {docs[0]['_id']}
83
+ - Timestamp: {docs[0]['timestamp']}
84
+ - Métricas guardadas: {bool(docs[0].get('metrics'))}
85
+ """)
86
+ return True
87
+
88
+ logger.warning(f"No se encontraron documentos recientes para {username}")
89
+ return False
90
+
91
+ except Exception as e:
92
+ logger.error(f"Error verificando almacenamiento: {str(e)}")
93
+ return False
94
+
95
+ def get_current_situation_analysis(username, limit=5):
96
+ """
97
+ Obtiene los análisis de situación actual de un usuario.
98
+ """
99
+ try:
100
+ collection = get_collection(COLLECTION_NAME)
101
+ if collection is None:
102
+ logger.error("No se pudo obtener la colección")
103
+ return []
104
+
105
+ # Buscar documentos
106
+ query = {'username': username, 'analysis_type': 'current_situation'}
107
+ cursor = collection.find(query).sort('timestamp', -1)
108
+
109
+ # Aplicar límite si se especifica
110
+ if limit:
111
+ cursor = cursor.limit(limit)
112
+
113
+ # Convertir cursor a lista
114
+ return list(cursor)
115
+
116
+ except Exception as e:
117
+ logger.error(f"Error obteniendo análisis de situación actual: {str(e)}")
118
+ return []
119
+
120
+ def get_recent_situation_analysis(username, limit=5):
121
+ """
122
+ Obtiene los análisis más recientes de un usuario.
123
+ """
124
+ try:
125
+ collection = get_collection(COLLECTION_NAME)
126
+ if collection is None:
127
+ return []
128
+
129
+ results = collection.find(
130
+ {'username': username}
131
+ ).sort('timestamp', -1).limit(limit)
132
+
133
+ return list(results)
134
+
135
+ except Exception as e:
136
+ logger.error(f"Error obteniendo análisis recientes: {str(e)}")
137
+ return []
modules/database/database_init.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. modules/database/database_init.py
2
+
3
+ import os
4
+ import logging
5
+ from azure.cosmos import CosmosClient
6
+ from pymongo import MongoClient
7
+ import certifi
8
+
9
+ logging.basicConfig(level=logging.DEBUG)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Variables globales para Cosmos DB SQL API
13
+ cosmos_client = None
14
+ user_database = None
15
+ user_container = None
16
+ application_requests_container = None
17
+ user_feedback_container = None
18
+ user_sessions_container = None
19
+
20
+ # Variables globales para Cosmos DB MongoDB API
21
+ mongo_client = None
22
+ mongo_db = None
23
+
24
+ ###################################################################
25
+ def verify_container_partition_key(container, expected_path):
26
+ """Verifica la configuración de partition key de un contenedor"""
27
+ try:
28
+ container_props = container.read()
29
+ partition_key_paths = container_props['partitionKey']['paths']
30
+ logger.info(f"Container: {container.id}, Partition Key Paths: {partition_key_paths}")
31
+ return expected_path in partition_key_paths
32
+ except Exception as e:
33
+ logger.error(f"Error verificando partition key en {container.id}: {str(e)}")
34
+ return False
35
+
36
+ ###################################################################
37
+ def get_container(container_name):
38
+ """Obtiene un contenedor específico"""
39
+ logger.info(f"Solicitando contenedor: {container_name}")
40
+
41
+ if not initialize_cosmos_sql_connection():
42
+ logger.error("No se pudo inicializar la conexión")
43
+ return None
44
+
45
+ # Verificar estado de los contenedores
46
+ containers_status = {
47
+ "users": user_container is not None,
48
+ "users_sessions": user_sessions_container is not None,
49
+ "application_requests": application_requests_container is not None,
50
+ "user_feedback": user_feedback_container is not None # Añadido
51
+ }
52
+
53
+ logger.info(f"Estado actual de los contenedores: {containers_status}")
54
+
55
+ # Mapear nombres a contenedores
56
+ containers = {
57
+ "users": user_container,
58
+ "users_sessions": user_sessions_container,
59
+ "application_requests": application_requests_container,
60
+ "user_feedback": user_feedback_container # Añadido
61
+ }
62
+
63
+ container = containers.get(container_name)
64
+
65
+ if container is None:
66
+ logger.error(f"Contenedor '{container_name}' no encontrado o no inicializado")
67
+ logger.error(f"Contenedores disponibles: {[k for k, v in containers_status.items() if v]}")
68
+ return None
69
+
70
+ logger.info(f"Contenedor '{container_name}' obtenido exitosamente")
71
+ return container
72
+ ###################################################################
73
+
74
+ def initialize_cosmos_sql_connection():
75
+ """Inicializa la conexión a Cosmos DB SQL API"""
76
+ global cosmos_client, user_database, user_container, user_sessions_container, application_requests_container, user_feedback_container # Añadida aquí user_feedback_container
77
+
78
+ try:
79
+ # Verificar conexión existente
80
+ if all([
81
+ cosmos_client,
82
+ user_database,
83
+ user_container,
84
+ user_sessions_container,
85
+ application_requests_container,
86
+ user_feedback_container
87
+ ]):
88
+ logger.debug("Todas las conexiones ya están inicializadas")
89
+ return True
90
+
91
+ # Obtener credenciales
92
+ cosmos_endpoint = os.environ.get("COSMOS_ENDPOINT")
93
+ cosmos_key = os.environ.get("COSMOS_KEY")
94
+
95
+ if not cosmos_endpoint or not cosmos_key:
96
+ raise ValueError("COSMOS_ENDPOINT y COSMOS_KEY deben estar configurados")
97
+
98
+ # Inicializar cliente y base de datos
99
+ cosmos_client = CosmosClient(cosmos_endpoint, cosmos_key)
100
+ user_database = cosmos_client.get_database_client("user_database")
101
+
102
+ # Inicializar contenedores
103
+ try:
104
+ user_container = user_database.get_container_client("users")
105
+ logger.info("Contenedor 'users' inicializado correctamente")
106
+ except Exception as e:
107
+ logger.error(f"Error inicializando contenedor 'users': {str(e)}")
108
+ user_container = None
109
+
110
+ try:
111
+ user_sessions_container = user_database.get_container_client("users_sessions")
112
+ logger.info("Contenedor 'users_sessions' inicializado correctamente")
113
+ except Exception as e:
114
+ logger.error(f"Error inicializando contenedor 'users_sessions': {str(e)}")
115
+ user_sessions_container = None
116
+
117
+ try:
118
+ application_requests_container = user_database.get_container_client("application_requests")
119
+ logger.info("Contenedor 'application_requests' inicializado correctamente")
120
+ except Exception as e:
121
+ logger.error(f"Error inicializando contenedor 'application_requests': {str(e)}")
122
+ application_requests_container = None
123
+
124
+ try:
125
+ user_feedback_container = user_database.get_container_client("user_feedback")
126
+ logger.info("Contenedor 'user_feedback' inicializado correctamente")
127
+ except Exception as e:
128
+ logger.error(f"Error inicializando contenedor 'user_feedback': {str(e)}")
129
+ user_feedback_container = None
130
+
131
+ # Verificar el estado de los contenedores
132
+ containers_status = {
133
+ 'users': user_container is not None,
134
+ 'users_sessions': user_sessions_container is not None,
135
+ 'application_requests': application_requests_container is not None,
136
+ 'user_feedback': user_feedback_container is not None
137
+ }
138
+
139
+ logger.info(f"Estado de los contenedores: {containers_status}")
140
+
141
+ if all(containers_status.values()):
142
+ logger.info("Todos los contenedores inicializados correctamente")
143
+ return True
144
+ else:
145
+ logger.error("No se pudieron inicializar todos los contenedores")
146
+ return False
147
+
148
+ except Exception as e:
149
+ logger.error(f"Error al conectar con Cosmos DB SQL API: {str(e)}")
150
+ return False
151
+
152
+
153
+ ###################################################################
154
+ def initialize_mongodb_connection():
155
+ """Inicializa la conexión a MongoDB"""
156
+ global mongo_client, mongo_db
157
+ try:
158
+ connection_string = os.getenv("MONGODB_CONNECTION_STRING")
159
+ if not connection_string:
160
+ raise ValueError("MONGODB_CONNECTION_STRING debe estar configurado")
161
+
162
+ mongo_client = MongoClient(
163
+ connection_string,
164
+ tls=True,
165
+ tlsCAFile=certifi.where(),
166
+ retryWrites=False,
167
+ serverSelectionTimeoutMS=5000,
168
+ connectTimeoutMS=10000,
169
+ socketTimeoutMS=10000
170
+ )
171
+
172
+ mongo_db = mongo_client['aideatext_db']
173
+ return True
174
+ except Exception as e:
175
+ logger.error(f"Error conectando a MongoDB: {str(e)}")
176
+ return False
177
+
178
+ ###################################################################
179
+ def initialize_database_connections():
180
+ """Inicializa todas las conexiones"""
181
+ return initialize_cosmos_sql_connection() and initialize_mongodb_connection()
182
+
183
+ ###################################################################
184
+ def get_mongodb():
185
+ """Obtiene la conexión MongoDB"""
186
+ if mongo_db is None:
187
+ initialize_mongodb_connection()
188
+ return mongo_db
modules/database/discourse_mongo_db.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/database/discourse_mongo_db.py
2
+ # Importaciones estándar
3
+ import io
4
+ import base64
5
+ from datetime import datetime, timezone
6
+ import logging
7
+
8
+ # Importaciones de terceros
9
+ import matplotlib.pyplot as plt
10
+
11
+ from .mongo_db import (
12
+ get_collection,
13
+ insert_document,
14
+ find_documents,
15
+ update_document,
16
+ delete_document
17
+ )
18
+
19
+ # Configuración del logger
20
+ logger = logging.getLogger(__name__)
21
+ COLLECTION_NAME = 'student_discourse_analysis'
22
+
23
+ ########################################################################
24
+ def store_student_discourse_result(username, text1, text2, analysis_result):
25
+ """
26
+ Guarda el resultado del análisis de discurso comparativo en MongoDB.
27
+ """
28
+ try:
29
+ # Los gráficos ya vienen en bytes, solo necesitamos codificar a base64
30
+ graph1_data = None
31
+ graph2_data = None
32
+ combined_graph_data = None
33
+
34
+ if 'graph1' in analysis_result and analysis_result['graph1'] is not None:
35
+ try:
36
+ graph1_data = base64.b64encode(analysis_result['graph1']).decode('utf-8')
37
+ except Exception as e:
38
+ logger.error(f"Error al codificar gráfico 1: {str(e)}")
39
+
40
+ if 'graph2' in analysis_result and analysis_result['graph2'] is not None:
41
+ try:
42
+ graph2_data = base64.b64encode(analysis_result['graph2']).decode('utf-8')
43
+ except Exception as e:
44
+ logger.error(f"Error al codificar gráfico 2: {str(e)}")
45
+
46
+ if 'combined_graph' in analysis_result and analysis_result['combined_graph'] is not None:
47
+ try:
48
+ combined_graph_data = base64.b64encode(analysis_result['combined_graph']).decode('utf-8')
49
+ except Exception as e:
50
+ logger.error(f"Error al codificar gráfico combinado: {str(e)}")
51
+
52
+ # Crear documento para MongoDB
53
+ analysis_document = {
54
+ 'username': username,
55
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
56
+ 'text1': text1,
57
+ 'text2': text2,
58
+ 'analysis_type': 'discourse',
59
+ 'key_concepts1': analysis_result.get('key_concepts1', []),
60
+ 'key_concepts2': analysis_result.get('key_concepts2', []),
61
+ 'graph1': graph1_data,
62
+ 'graph2': graph2_data,
63
+ 'combined_graph': combined_graph_data
64
+ }
65
+
66
+ # Insertar en MongoDB
67
+ result = insert_document(COLLECTION_NAME, analysis_document)
68
+ if result:
69
+ logger.info(f"Análisis del discurso guardado con ID: {result} para el usuario: {username}")
70
+ return True
71
+
72
+ logger.error("No se pudo insertar el documento en MongoDB")
73
+ return False
74
+
75
+ except Exception as e:
76
+ logger.error(f"Error al guardar el análisis del discurso: {str(e)}")
77
+ return False
78
+
79
+
80
+
81
+ #################################################################################
82
+ def get_student_discourse_analysis(username, limit=10):
83
+ """
84
+ Recupera los análisis del discurso de un estudiante.
85
+ """
86
+ try:
87
+ # Obtener la colección
88
+ collection = get_collection(COLLECTION_NAME)
89
+ if collection is None: # Cambiado de if not collection a if collection is None
90
+ logger.error("No se pudo obtener la colección discourse")
91
+ return []
92
+
93
+ # Consulta
94
+ query = {
95
+ "username": username,
96
+ "analysis_type": "discourse"
97
+ }
98
+
99
+ # Campos a recuperar
100
+ projection = {
101
+ "timestamp": 1,
102
+ "combined_graph": 1,
103
+ "_id": 1
104
+ }
105
+
106
+ # Ejecutar consulta
107
+ try:
108
+ cursor = collection.find(query, projection).sort("timestamp", -1)
109
+ if limit:
110
+ cursor = cursor.limit(limit)
111
+
112
+ # Convertir cursor a lista
113
+ results = list(cursor)
114
+ logger.info(f"Recuperados {len(results)} análisis del discurso para {username}")
115
+ return results
116
+
117
+ except Exception as db_error:
118
+ logger.error(f"Error en la consulta a MongoDB: {str(db_error)}")
119
+ return []
120
+
121
+ except Exception as e:
122
+ logger.error(f"Error recuperando análisis del discurso: {str(e)}")
123
+ return []
124
+ #####################################################################################
125
+
126
+ def get_student_discourse_data(username):
127
+ """
128
+ Obtiene un resumen de los análisis del discurso de un estudiante.
129
+ """
130
+ try:
131
+ analyses = get_student_discourse_analysis(username, limit=None)
132
+ formatted_analyses = []
133
+
134
+ for analysis in analyses:
135
+ formatted_analysis = {
136
+ 'timestamp': analysis['timestamp'],
137
+ 'text1': analysis.get('text1', ''),
138
+ 'text2': analysis.get('text2', ''),
139
+ 'key_concepts1': analysis.get('key_concepts1', []),
140
+ 'key_concepts2': analysis.get('key_concepts2', [])
141
+ }
142
+ formatted_analyses.append(formatted_analysis)
143
+
144
+ return {'entries': formatted_analyses}
145
+
146
+ except Exception as e:
147
+ logger.error(f"Error al obtener datos del discurso: {str(e)}")
148
+ return {'entries': []}
149
+
150
+ ###########################################################################
151
+ def update_student_discourse_analysis(analysis_id, update_data):
152
+ """
153
+ Actualiza un análisis del discurso existente.
154
+ """
155
+ try:
156
+ query = {"_id": analysis_id}
157
+ update = {"$set": update_data}
158
+ return update_document(COLLECTION_NAME, query, update)
159
+ except Exception as e:
160
+ logger.error(f"Error al actualizar análisis del discurso: {str(e)}")
161
+ return False
162
+
163
+ ###########################################################################
164
+ def delete_student_discourse_analysis(analysis_id):
165
+ """
166
+ Elimina un análisis del discurso.
167
+ """
168
+ try:
169
+ query = {"_id": analysis_id}
170
+ return delete_document(COLLECTION_NAME, query)
171
+ except Exception as e:
172
+ logger.error(f"Error al eliminar análisis del discurso: {str(e)}")
173
+ return False
modules/database/mongo_db.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .database_init import get_mongodb
2
+ import logging
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ def get_collection(collection_name):
7
+ try:
8
+ db = get_mongodb()
9
+ if db is None:
10
+ logger.error(f"No se pudo obtener la base de datos para {collection_name}")
11
+ return None
12
+
13
+ collection = db[collection_name]
14
+ logger.info(f"Colección {collection_name} obtenida exitosamente")
15
+ return collection
16
+
17
+ except Exception as e:
18
+ logger.error(f"Error al obtener colección {collection_name}: {str(e)}")
19
+ return None
20
+
21
+ def insert_document(collection_name, document):
22
+ collection = get_collection(collection_name)
23
+ try:
24
+ result = collection.insert_one(document)
25
+ logger.info(f"Documento insertado en {collection_name} con ID: {result.inserted_id}")
26
+ return result.inserted_id
27
+ except Exception as e:
28
+ logger.error(f"Error al insertar documento en {collection_name}: {str(e)}")
29
+ return None
30
+
31
+ def find_documents(collection_name, query, sort=None, limit=None):
32
+ collection = get_collection(collection_name)
33
+ try:
34
+ cursor = collection.find(query)
35
+ if sort:
36
+ cursor = cursor.sort(sort)
37
+ if limit:
38
+ cursor = cursor.limit(limit)
39
+ return list(cursor)
40
+ except Exception as e:
41
+ logger.error(f"Error al buscar documentos en {collection_name}: {str(e)}")
42
+ return []
43
+
44
+ def update_document(collection_name, query, update):
45
+ collection = get_collection(collection_name)
46
+ try:
47
+ result = collection.update_one(query, update)
48
+ logger.info(f"Documento actualizado en {collection_name}: {result.modified_count} modificado(s)")
49
+ return result.modified_count
50
+ except Exception as e:
51
+ logger.error(f"Error al actualizar documento en {collection_name}: {str(e)}")
52
+ return 0
53
+
54
+ def delete_document(collection_name, query):
55
+ collection = get_collection(collection_name)
56
+ try:
57
+ result = collection.delete_one(query)
58
+ logger.info(f"Documento eliminado de {collection_name}: {result.deleted_count} eliminado(s)")
59
+ return result.deleted_count
60
+ except Exception as e:
61
+ logger.error(f"Error al eliminar documento de {collection_name}: {str(e)}")
62
+ return 0
modules/database/morphosintax_mongo_db.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #/modules/database/morphosintax_mongo_db.py
2
+ from .mongo_db import insert_document, find_documents, update_document, delete_document
3
+ from ..utils.svg_to_png_converter import process_and_save_svg_diagrams
4
+ from datetime import datetime, timezone
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ COLLECTION_NAME = 'student_morphosyntax_analysis'
10
+
11
+ def store_student_morphosyntax_result(username, text, arc_diagrams):
12
+ analysis_document = {
13
+ 'username': username,
14
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
15
+ 'text': text,
16
+ 'arc_diagrams': arc_diagrams,
17
+ 'analysis_type': 'morphosyntax'
18
+ }
19
+
20
+ result = insert_document(COLLECTION_NAME, analysis_document)
21
+ if result:
22
+ # Procesar y guardar los diagramas SVG como PNG
23
+ png_ids = process_and_save_svg_diagrams(username, str(result), arc_diagrams)
24
+
25
+ # Actualizar el documento con los IDs de los PNGs
26
+ update_document(COLLECTION_NAME, {'_id': result}, {'$set': {'png_diagram_ids': png_ids}})
27
+
28
+ logger.info(f"Análisis morfosintáctico del estudiante guardado con ID: {result} para el usuario: {username}")
29
+ return True
30
+ return False
31
+
32
+ def get_student_morphosyntax_analysis(username, limit=10):
33
+ query = {"username": username, "analysis_type": "morphosyntax"}
34
+ return find_documents(COLLECTION_NAME, query, sort=[("timestamp", -1)], limit=limit)
35
+
36
+ def update_student_morphosyntax_analysis(analysis_id, update_data):
37
+ query = {"_id": analysis_id}
38
+ update = {"$set": update_data}
39
+ return update_document(COLLECTION_NAME, query, update)
40
+
41
+ def delete_student_morphosyntax_analysis(analysis_id):
42
+ query = {"_id": analysis_id}
43
+ return delete_document(COLLECTION_NAME, query)
44
+
45
+ def get_student_morphosyntax_data(username):
46
+ analyses = get_student_morphosyntax_analysis(username, limit=None) # Obtener todos los análisis
47
+ return {
48
+ 'entries': analyses
49
+ }
modules/database/morphosintaxis_export.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+ from reportlab.lib import colors
3
+ from reportlab.lib.pagesizes import letter
4
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak
5
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
6
+ from reportlab.lib.units import cm
7
+ from svglib.svglib import svg2rlg
8
+ from reportlab.graphics import renderPM
9
+ import base64
10
+ import cairosvg
11
+ from reportlab.graphics import renderPDF
12
+ from reportlab.lib.utils import ImageReader
13
+
14
+ #importaciones locales
15
+ from .morphosintax_mongo_db import get_student_morphosyntax_data
16
+ from .chat_db import get_chat_history
17
+
18
+ # Placeholder para el logo
19
+ LOGO_PATH = "assets\img\logo_92x92.png" # Reemplaza esto con la ruta real de tu logo
20
+
21
+ # Definir el tamaño de página carta manualmente (612 x 792 puntos)
22
+ LETTER_SIZE = (612, 792)
23
+
24
+ def add_logo(canvas, doc):
25
+ logo = Image(LOGO_PATH, width=2*cm, height=2*cm)
26
+ logo.drawOn(canvas, doc.leftMargin, doc.height + doc.topMargin - 0.5*cm)
27
+
28
+ def export_user_interactions(username, analysis_type):
29
+ # Obtener historial de chat (que ahora incluye los análisis morfosintácticos)
30
+ chat_history = get_chat_history(username, analysis_type)
31
+
32
+ # Crear un PDF
33
+ buffer = BytesIO()
34
+ doc = SimpleDocTemplate(
35
+ buffer,
36
+ pagesize=letter,
37
+ rightMargin=2*cm,
38
+ leftMargin=2*cm,
39
+ topMargin=2*cm,
40
+ bottomMargin=2*cm
41
+ )
42
+
43
+ story = []
44
+ styles = getSampleStyleSheet()
45
+
46
+ # Título
47
+ story.append(Paragraph(f"Interacciones de {username} - Análisis {analysis_type}", styles['Title']))
48
+ story.append(Spacer(1, 0.5*cm))
49
+
50
+ # Historial del chat y análisis
51
+ for entry in chat_history:
52
+ for message in entry['messages']:
53
+ role = message['role']
54
+ content = message['content']
55
+ story.append(Paragraph(f"<b>{role.capitalize()}:</b> {content}", styles['BodyText']))
56
+ story.append(Spacer(1, 0.25*cm))
57
+
58
+ # Si hay visualizaciones (diagramas SVG), convertirlas a imagen y añadirlas
59
+ if 'visualizations' in message and message['visualizations']:
60
+ for svg in message['visualizations']:
61
+ drawing = svg2rlg(BytesIO(svg.encode('utf-8')))
62
+ img_data = BytesIO()
63
+ renderPM.drawToFile(drawing, img_data, fmt="PNG")
64
+ img_data.seek(0)
65
+ img = Image(img_data, width=15*cm, height=7.5*cm)
66
+ story.append(img)
67
+ story.append(Spacer(1, 0.5*cm))
68
+
69
+ story.append(PageBreak())
70
+
71
+ # Construir el PDF
72
+ doc.build(story)
73
+ buffer.seek(0)
74
+ return buffer
75
+
76
+ # Uso en Streamlit:
77
+ # pdf_buffer = export_user_interactions(username, 'morphosyntax')
78
+ # st.download_button(label="Descargar PDF", data=pdf_buffer, file_name="interacciones.pdf", mime="application/pdf")
modules/database/morphosintaxis_export_v1.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # database_export.py
2
+
3
+ import pandas as pd
4
+ import matplotlib.pyplot as plt
5
+ from io import BytesIO
6
+ #importaciones locales
7
+ from .morphosintax_mongo_db import get_student_morphosyntax_analysis
8
+ from .chat_db import get_chat_history
9
+
10
+
11
+ def export_user_interactions(username, analysis_type):
12
+ # Obtener historial de chat (que ahora incluye los análisis morfosintácticos)
13
+ chat_history = get_chat_history(username, analysis_type)
14
+
15
+ # Crear un PDF
16
+ buffer = BytesIO()
17
+ doc = SimpleDocTemplate(
18
+ buffer,
19
+ pagesize=letter,
20
+ rightMargin=2*cm,
21
+ leftMargin=2*cm,
22
+ topMargin=2*cm,
23
+ bottomMargin=2*cm
24
+ )
25
+
26
+ story = []
27
+ styles = getSampleStyleSheet()
28
+
29
+ # Título
30
+ story.append(Paragraph(f"Interacciones de {username} - Análisis {analysis_type}", styles['Title']))
31
+ story.append(Spacer(1, 0.5*cm))
32
+
33
+ # Historial del chat y análisis
34
+ for entry in chat_history:
35
+ for message in entry['messages']:
36
+ role = message['role']
37
+ content = message['content']
38
+ story.append(Paragraph(f"<b>{role.capitalize()}:</b> {content}", styles['BodyText']))
39
+ story.append(Spacer(1, 0.25*cm))
40
+
41
+ # Si hay visualizaciones (diagramas SVG), convertirlas a imagen y añadirlas
42
+ if 'visualizations' in message and message['visualizations']:
43
+ for svg in message['visualizations']:
44
+ drawing = svg2rlg(BytesIO(svg.encode('utf-8')))
45
+ img_data = BytesIO()
46
+ renderPM.drawToFile(drawing, img_data, fmt="PNG")
47
+ img_data.seek(0)
48
+ img = Image(img_data, width=15*cm, height=7.5*cm)
49
+ story.append(img)
50
+ story.append(Spacer(1, 0.5*cm))
51
+
52
+ story.append(PageBreak())
53
+
54
+ # Construir el PDF
55
+ doc.build(story)
56
+ buffer.seek(0)
57
+ return buffer
58
+
59
+ #def export_user_interactions(username, analysis_type):
60
+ # Obtener análisis morfosintáctico
61
+ #morphosyntax_data = get_student_morphosyntax_analysis(username)
62
+
63
+ # Obtener historial de chat
64
+ #chat_history = get_chat_history(username, analysis_type)
65
+
66
+ # Crear un DataFrame con los datos
67
+ #df = pd.DataFrame({
68
+ # 'Timestamp': [entry['timestamp'] for entry in chat_history],
69
+ # 'Role': [msg['role'] for entry in chat_history for msg in entry['messages']],
70
+ # 'Content': [msg['content'] for entry in chat_history for msg in entry['messages']]
71
+ #})
72
+
73
+ # Crear un PDF
74
+ #buffer = BytesIO()
75
+ #plt.figure(figsize=(12, 6))
76
+ #plt.axis('off')
77
+ #plt.text(0.5, 0.98, f"Interacciones de {username} - Análisis {analysis_type}", ha='center', va='top', fontsize=16)
78
+ #plt.text(0.5, 0.95, f"Total de interacciones: {len(df)}", ha='center', va='top', fontsize=12)
79
+
80
+ # Añadir tabla con las interacciones
81
+ #plt.table(cellText=df.values, colLabels=df.columns, cellLoc='center', loc='center')
82
+
83
+ # Añadir diagramas de arco si es análisis morfosintáctico
84
+ #if analysis_type == 'morphosyntax' and morphosyntax_data:
85
+ # for i, analysis in enumerate(morphosyntax_data):
86
+ # plt.figure(figsize=(12, 6))
87
+ # plt.axis('off')
88
+ # plt.text(0.5, 0.98, f"Diagrama de Arco {i+1}", ha='center', va='top', fontsize=16)
89
+ # plt.imshow(analysis['arc_diagrams'][0]) # Asumiendo que arc_diagrams es una lista de imágenes
90
+
91
+ #plt.savefig(buffer, format='pdf', bbox_inches='tight')
92
+ #buffer.seek(0)
93
+ #return buffer
94
+
95
+ # Uso:
96
+ # pdf_buffer = export_user_interactions(username, 'morphosyntax')
97
+ # st.download_button(label="Descargar PDF", data=pdf_buffer, file_name="interacciones.pdf", mime="application/pdf")
modules/database/morphosyntax_iterative_mongo_db.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/database/morphosyntax_iterative_mongo_db.py
2
+
3
+
4
+ from datetime import datetime, timezone
5
+ import logging
6
+ from bson import ObjectId # <--- Importar ObjectId
7
+ from .mongo_db import get_collection, insert_document, find_documents, update_document, delete_document
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ BASE_COLLECTION = 'student_morphosyntax_analysis_base'
12
+ ITERATION_COLLECTION = 'student_morphosyntax_iterations'
13
+
14
+ def store_student_morphosyntax_base(username, text, arc_diagrams):
15
+ """Almacena el análisis morfosintáctico base y retorna su ObjectId."""
16
+ try:
17
+ base_document = {
18
+ 'username': username,
19
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
20
+ 'text': text,
21
+ 'arc_diagrams': arc_diagrams,
22
+ 'analysis_type': 'morphosyntax_base',
23
+ 'has_iterations': False
24
+ }
25
+ collection = get_collection(BASE_COLLECTION)
26
+ result = collection.insert_one(base_document)
27
+
28
+ logger.info(f"Análisis base guardado para {username}")
29
+ # Retornamos el ObjectId directamente (NO str)
30
+ return result.inserted_id
31
+
32
+ except Exception as e:
33
+ logger.error(f"Error almacenando análisis base: {str(e)}")
34
+ return None
35
+
36
+ def store_student_morphosyntax_iteration(username, base_id, original_text, iteration_text, arc_diagrams):
37
+ """
38
+ Almacena una iteración de análisis morfosintáctico.
39
+ base_id: ObjectId de la base (o string convertible a ObjectId).
40
+ """
41
+ try:
42
+ # Convertir a ObjectId si viene como string
43
+ if isinstance(base_id, str):
44
+ base_id = ObjectId(base_id)
45
+
46
+ iteration_document = {
47
+ 'username': username,
48
+ 'base_id': base_id, # Guardar el ObjectId en la iteración
49
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
50
+ 'original_text': original_text,
51
+ 'iteration_text': iteration_text,
52
+ 'arc_diagrams': arc_diagrams,
53
+ 'analysis_type': 'morphosyntax_iteration'
54
+ }
55
+ collection = get_collection(ITERATION_COLLECTION)
56
+ result = collection.insert_one(iteration_document)
57
+
58
+ # Actualizar documento base (usando ObjectId)
59
+ base_collection = get_collection(BASE_COLLECTION)
60
+ base_collection.update_one(
61
+ {'_id': base_id, 'username': username},
62
+ {'$set': {'has_iterations': True}}
63
+ )
64
+
65
+ logger.info(f"Iteración guardada para {username}, base_id: {base_id}")
66
+ return result.inserted_id # Retornar el ObjectId de la iteración
67
+
68
+ except Exception as e:
69
+ logger.error(f"Error almacenando iteración: {str(e)}")
70
+ return None
71
+
72
+ def get_student_morphosyntax_analysis(username, limit=10):
73
+ """
74
+ Obtiene los análisis base y sus iteraciones.
75
+ Returns: Lista de análisis con sus iteraciones.
76
+ """
77
+ try:
78
+ base_collection = get_collection(BASE_COLLECTION)
79
+ base_query = {
80
+ "username": username,
81
+ "analysis_type": "morphosyntax_base"
82
+ }
83
+ base_analyses = list(
84
+ base_collection.find(base_query).sort("timestamp", -1).limit(limit)
85
+ )
86
+
87
+ # Para cada análisis base, obtener sus iteraciones
88
+ iteration_collection = get_collection(ITERATION_COLLECTION)
89
+ for analysis in base_analyses:
90
+ base_id = analysis['_id']
91
+ # Buscar iteraciones con base_id = ObjectId
92
+ iterations = list(
93
+ iteration_collection.find({"base_id": base_id}).sort("timestamp", -1)
94
+ )
95
+ analysis['iterations'] = iterations
96
+
97
+ return base_analyses
98
+
99
+ except Exception as e:
100
+ logger.error(f"Error obteniendo análisis: {str(e)}")
101
+ return []
102
+
103
+ def update_student_morphosyntax_analysis(analysis_id, is_base, update_data):
104
+ """
105
+ Actualiza un análisis base o iteración.
106
+ analysis_id puede ser un ObjectId o string.
107
+ """
108
+ from bson import ObjectId
109
+
110
+ try:
111
+ collection_name = BASE_COLLECTION if is_base else ITERATION_COLLECTION
112
+ collection = get_collection(collection_name)
113
+
114
+ if isinstance(analysis_id, str):
115
+ analysis_id = ObjectId(analysis_id)
116
+
117
+ query = {"_id": analysis_id}
118
+ update = {"$set": update_data}
119
+
120
+ result = update_document(collection_name, query, update)
121
+ return result
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error actualizando análisis: {str(e)}")
125
+ return False
126
+
127
+ def delete_student_morphosyntax_analysis(analysis_id, is_base):
128
+ """
129
+ Elimina un análisis base o iteración.
130
+ Si es base, también elimina todas sus iteraciones.
131
+ """
132
+ from bson import ObjectId
133
+
134
+ try:
135
+ if isinstance(analysis_id, str):
136
+ analysis_id = ObjectId(analysis_id)
137
+
138
+ if is_base:
139
+ # Eliminar iteraciones vinculadas
140
+ iteration_collection = get_collection(ITERATION_COLLECTION)
141
+ iteration_collection.delete_many({"base_id": analysis_id})
142
+
143
+ # Luego eliminar el análisis base
144
+ collection = get_collection(BASE_COLLECTION)
145
+ else:
146
+ collection = get_collection(ITERATION_COLLECTION)
147
+
148
+ query = {"_id": analysis_id}
149
+ result = delete_document(collection.name, query)
150
+ return result
151
+
152
+ except Exception as e:
153
+ logger.error(f"Error eliminando análisis: {str(e)}")
154
+ return False
155
+
156
+ def get_student_morphosyntax_data(username):
157
+ """
158
+ Obtiene todos los datos de análisis morfosintáctico de un estudiante.
159
+ Returns: Diccionario con todos los análisis y sus iteraciones.
160
+ """
161
+ try:
162
+ analyses = get_student_morphosyntax_analysis(username, limit=None)
163
+ return {
164
+ 'entries': analyses,
165
+ 'total_analyses': len(analyses),
166
+ 'has_iterations': any(a.get('has_iterations', False) for a in analyses)
167
+ }
168
+
169
+ except Exception as e:
170
+ logger.error(f"Error obteniendo datos del estudiante: {str(e)}")
171
+ return {'entries': [], 'total_analyses': 0, 'has_iterations': False}
modules/database/semantic_export.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+ from reportlab.lib import colors
3
+ from reportlab.lib.pagesizes import letter
4
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak
5
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
6
+ from reportlab.lib.units import cm
7
+ from svglib.svglib import svg2rlg
8
+ from reportlab.graphics import renderPM
9
+ import base64
10
+ import cairosvg
11
+ from reportlab.graphics import renderPDF
12
+ from reportlab.lib.utils import ImageReader
13
+
14
+ #importaciones locales
15
+ from .semantic_mongo_db import get_student_semantic_data
16
+ from .chat_db import get_chat_history
17
+
18
+ # Placeholder para el logo
19
+ LOGO_PATH = "assets\img\logo_92x92.png" # Reemplaza esto con la ruta real de tu logo
20
+
21
+ # Definir el tamaño de página carta manualmente (612 x 792 puntos)
22
+ LETTER_SIZE = (612, 792)
23
+
24
+ def add_logo(canvas, doc):
25
+ logo = Image(LOGO_PATH, width=2*cm, height=2*cm)
26
+ logo.drawOn(canvas, doc.leftMargin, doc.height + doc.topMargin - 0.5*cm)
27
+
28
+ def export_user_interactions(username, analysis_type):
29
+ # Obtener historial de chat (que ahora incluye los análisis morfosintácticos)
30
+ chat_history = get_chat_history(username, analysis_type)
31
+
32
+ # Crear un PDF
33
+ buffer = BytesIO()
34
+ doc = SimpleDocTemplate(
35
+ buffer,
36
+ pagesize=letter,
37
+ rightMargin=2*cm,
38
+ leftMargin=2*cm,
39
+ topMargin=2*cm,
40
+ bottomMargin=2*cm
41
+ )
42
+
43
+ story = []
44
+ styles = getSampleStyleSheet()
45
+
46
+ # Título
47
+ story.append(Paragraph(f"Interacciones de {username} - Análisis {analysis_type}", styles['Title']))
48
+ story.append(Spacer(1, 0.5*cm))
49
+
50
+ # Historial del chat y análisis
51
+ for entry in chat_history:
52
+ for message in entry['messages']:
53
+ role = message['role']
54
+ content = message['content']
55
+ story.append(Paragraph(f"<b>{role.capitalize()}:</b> {content}", styles['BodyText']))
56
+ story.append(Spacer(1, 0.25*cm))
57
+
58
+ # Si hay visualizaciones (diagramas SVG), convertirlas a imagen y añadirlas
59
+ if 'visualizations' in message and message['visualizations']:
60
+ for svg in message['visualizations']:
61
+ drawing = svg2rlg(BytesIO(svg.encode('utf-8')))
62
+ img_data = BytesIO()
63
+ renderPM.drawToFile(drawing, img_data, fmt="PNG")
64
+ img_data.seek(0)
65
+ img = Image(img_data, width=15*cm, height=7.5*cm)
66
+ story.append(img)
67
+ story.append(Spacer(1, 0.5*cm))
68
+
69
+ story.append(PageBreak())
70
+
71
+ # Construir el PDF
72
+ doc.build(story)
73
+ buffer.seek(0)
74
+ return buffer
75
+
76
+ # Uso en Streamlit:
77
+ # pdf_buffer = export_user_interactions(username, 'morphosyntax')
78
+ # st.download_button(label="Descargar PDF", data=pdf_buffer, file_name="interacciones.pdf", mime="application/pdf")
modules/database/semantic_mongo_db.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #/modules/database/semantic_mongo_db.py
2
+
3
+ # Importaciones estándar
4
+ import io
5
+ import base64
6
+ from datetime import datetime, timezone
7
+ import logging
8
+
9
+ # Importaciones de terceros
10
+ import matplotlib.pyplot as plt
11
+
12
+ # Importaciones locales
13
+ from .mongo_db import (
14
+ get_collection,
15
+ insert_document,
16
+ find_documents,
17
+ update_document,
18
+ delete_document
19
+ )
20
+
21
+ # Configuración del logger
22
+ logger = logging.getLogger(__name__) # Cambiado de name a __name__
23
+ COLLECTION_NAME = 'student_semantic_analysis'
24
+
25
+ def store_student_semantic_result(username, text, analysis_result):
26
+ """
27
+ Guarda el resultado del análisis semántico en MongoDB.
28
+ """
29
+ try:
30
+ # El gráfico ya viene en bytes, solo necesitamos codificarlo a base64
31
+ concept_graph_data = None
32
+ if 'concept_graph' in analysis_result and analysis_result['concept_graph'] is not None:
33
+ try:
34
+ # Ya está en bytes, solo codificar a base64
35
+ concept_graph_data = base64.b64encode(analysis_result['concept_graph']).decode('utf-8')
36
+ except Exception as e:
37
+ logger.error(f"Error al codificar gráfico conceptual: {str(e)}")
38
+
39
+ # Crear documento para MongoDB
40
+ analysis_document = {
41
+ 'username': username,
42
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
43
+ 'text': text,
44
+ 'analysis_type': 'semantic',
45
+ 'key_concepts': analysis_result.get('key_concepts', []),
46
+ 'concept_graph': concept_graph_data
47
+ }
48
+
49
+ # Insertar en MongoDB
50
+ result = insert_document(COLLECTION_NAME, analysis_document)
51
+ if result:
52
+ logger.info(f"Análisis semántico guardado con ID: {result} para el usuario: {username}")
53
+ return True
54
+
55
+ logger.error("No se pudo insertar el documento en MongoDB")
56
+ return False
57
+
58
+ except Exception as e:
59
+ logger.error(f"Error al guardar el análisis semántico: {str(e)}")
60
+ return False
61
+
62
+ ####################################################################################
63
+ def get_student_semantic_analysis(username, limit=10):
64
+ """
65
+ Recupera los análisis semánticos de un estudiante.
66
+ """
67
+ try:
68
+ # Obtener la colección
69
+ collection = get_collection(COLLECTION_NAME)
70
+ if collection is None: # Cambiado de if not collection a if collection is None
71
+ logger.error("No se pudo obtener la colección semantic")
72
+ return []
73
+
74
+ # Consulta
75
+ query = {
76
+ "username": username,
77
+ "analysis_type": "semantic"
78
+ }
79
+
80
+ # Campos a recuperar
81
+ projection = {
82
+ "timestamp": 1,
83
+ "concept_graph": 1,
84
+ "_id": 1
85
+ }
86
+
87
+ # Ejecutar consulta
88
+ try:
89
+ cursor = collection.find(query, projection).sort("timestamp", -1)
90
+ if limit:
91
+ cursor = cursor.limit(limit)
92
+
93
+ # Convertir cursor a lista
94
+ results = list(cursor)
95
+ logger.info(f"Recuperados {len(results)} análisis semánticos para {username}")
96
+ return results
97
+
98
+ except Exception as db_error:
99
+ logger.error(f"Error en la consulta a MongoDB: {str(db_error)}")
100
+ return []
101
+
102
+ except Exception as e:
103
+ logger.error(f"Error recuperando análisis semántico: {str(e)}")
104
+ return []
105
+ ####################################################################################################
106
+
107
+
108
+ def update_student_semantic_analysis(analysis_id, update_data):
109
+ """
110
+ Actualiza un análisis semántico existente.
111
+ Args:
112
+ analysis_id: ID del análisis a actualizar
113
+ update_data: Datos a actualizar
114
+ """
115
+ query = {"_id": analysis_id}
116
+ update = {"$set": update_data}
117
+ return update_document(COLLECTION_NAME, query, update)
118
+
119
+ def delete_student_semantic_analysis(analysis_id):
120
+ """
121
+ Elimina un análisis semántico.
122
+ Args:
123
+ analysis_id: ID del análisis a eliminar
124
+ """
125
+ query = {"_id": analysis_id}
126
+ return delete_document(COLLECTION_NAME, query)
127
+
128
+ def get_student_semantic_data(username):
129
+ """
130
+ Obtiene todos los análisis semánticos de un estudiante.
131
+ Args:
132
+ username: Nombre del usuario
133
+ Returns:
134
+ dict: Diccionario con todos los análisis del estudiante
135
+ """
136
+ analyses = get_student_semantic_analysis(username, limit=None)
137
+
138
+ formatted_analyses = []
139
+ for analysis in analyses:
140
+ formatted_analysis = {
141
+ 'timestamp': analysis['timestamp'],
142
+ 'text': analysis['text'],
143
+ 'key_concepts': analysis['key_concepts'],
144
+ 'entities': analysis['entities']
145
+ # No incluimos los gráficos en el resumen general
146
+ }
147
+ formatted_analyses.append(formatted_analysis)
148
+
149
+ return {
150
+ 'entries': formatted_analyses
151
+ }
152
+
153
+ # Exportar las funciones necesarias
154
+ __all__ = [
155
+ 'store_student_semantic_result',
156
+ 'get_student_semantic_analysis',
157
+ 'update_student_semantic_analysis',
158
+ 'delete_student_semantic_analysis',
159
+ 'get_student_semantic_data'
160
+ ]
modules/database/sql_db.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/database/sql_db.py
2
+
3
+ from .database_init import get_container
4
+ from datetime import datetime, timezone
5
+ import logging
6
+ import bcrypt
7
+ import uuid
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ #########################################
12
+ def get_user(username, role=None):
13
+ container = get_container("users")
14
+ try:
15
+ query = f"SELECT * FROM c WHERE c.id = '{username}'"
16
+ if role:
17
+ query += f" AND c.role = '{role}'"
18
+ items = list(container.query_items(query=query))
19
+ return items[0] if items else None
20
+ except Exception as e:
21
+ logger.error(f"Error al obtener usuario {username}: {str(e)}")
22
+ return None
23
+
24
+
25
+ #########################################
26
+ def get_admin_user(username):
27
+ return get_user(username, role='Administrador')
28
+
29
+
30
+ #########################################
31
+ def get_student_user(username):
32
+ return get_user(username, role='Estudiante')
33
+
34
+
35
+ #########################################
36
+ def get_teacher_user(username):
37
+ return get_user(username, role='Profesor')
38
+
39
+
40
+ #########################################
41
+ def create_user(username, password, role, additional_info=None):
42
+ """Crea un nuevo usuario"""
43
+ container = get_container("users")
44
+ if not container:
45
+ logger.error("No se pudo obtener el contenedor de usuarios")
46
+ return False
47
+
48
+ try:
49
+ user_data = {
50
+ 'id': username,
51
+ 'password': password,
52
+ 'role': role,
53
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
54
+ 'additional_info': additional_info or {},
55
+ 'partitionKey': username # Agregar partition key
56
+ }
57
+
58
+ # Crear item sin especificar partition_key en el método
59
+ container.create_item(body=user_data)
60
+ logger.info(f"Usuario {role} creado: {username}")
61
+ return True
62
+
63
+ except Exception as e:
64
+ logger.error(f"Error al crear usuario {role}: {str(e)}")
65
+ return False
66
+
67
+ #########################################
68
+ def create_student_user(username, password, additional_info=None):
69
+ return create_user(username, password, 'Estudiante', additional_info)
70
+
71
+ #########################################
72
+ def create_teacher_user(username, password, additional_info=None):
73
+ return create_user(username, password, 'Profesor', additional_info)
74
+
75
+ #########################################
76
+ def create_admin_user(username, password, additional_info=None):
77
+ return create_user(username, password, 'Administrador', additional_info)
78
+
79
+ #########################################
80
+ def record_login(username):
81
+ """Registra el inicio de sesión de un usuario"""
82
+ try:
83
+ container = get_container("users_sessions")
84
+ if not container:
85
+ logger.error("No se pudo obtener el contenedor users_sessions")
86
+ return None
87
+
88
+ session_id = str(uuid.uuid4())
89
+ session_doc = {
90
+ "id": session_id,
91
+ "type": "session",
92
+ "username": username,
93
+ "loginTime": datetime.now(timezone.utc).isoformat(),
94
+ "additional_info": {},
95
+ "partitionKey": username
96
+ }
97
+
98
+ result = container.create_item(body=session_doc)
99
+ logger.info(f"Sesión {session_id} registrada para {username}")
100
+ return session_id
101
+ except Exception as e:
102
+ logger.error(f"Error registrando login: {str(e)}")
103
+ return None
104
+
105
+ #########################################
106
+ def record_logout(username, session_id):
107
+ """Registra el cierre de sesión y calcula la duración"""
108
+ try:
109
+ container = get_container("users_sessions")
110
+ if not container:
111
+ logger.error("No se pudo obtener el contenedor users_sessions")
112
+ return False
113
+
114
+ query = "SELECT * FROM c WHERE c.id = @id AND c.username = @username"
115
+ params = [
116
+ {"name": "@id", "value": session_id},
117
+ {"name": "@username", "value": username}
118
+ ]
119
+
120
+ items = list(container.query_items(
121
+ query=query,
122
+ parameters=params
123
+ ))
124
+
125
+ if not items:
126
+ logger.warning(f"Sesión no encontrada: {session_id}")
127
+ return False
128
+
129
+ session = items[0]
130
+ login_time = datetime.fromisoformat(session['loginTime'].rstrip('Z'))
131
+ logout_time = datetime.now(timezone.utc)
132
+ duration = int((logout_time - login_time).total_seconds())
133
+
134
+ session.update({
135
+ "logoutTime": logout_time.isoformat(),
136
+ "sessionDuration": duration,
137
+ "partitionKey": username
138
+ })
139
+
140
+ container.upsert_item(body=session)
141
+ logger.info(f"Sesión {session_id} cerrada para {username}, duración: {duration}s")
142
+ return True
143
+ except Exception as e:
144
+ logger.error(f"Error registrando logout: {str(e)}")
145
+ return False
146
+
147
+ #########################################
148
+ def get_recent_sessions(limit=10):
149
+ """Obtiene las sesiones más recientes"""
150
+ try:
151
+ container = get_container("users_sessions")
152
+ if not container:
153
+ logger.error("No se pudo obtener el contenedor users_sessions")
154
+ return []
155
+
156
+ query = """
157
+ SELECT c.username, c.loginTime, c.logoutTime, c.sessionDuration
158
+ FROM c
159
+ WHERE c.type = 'session'
160
+ ORDER BY c.loginTime DESC
161
+ OFFSET 0 LIMIT @limit
162
+ """
163
+
164
+ sessions = list(container.query_items(
165
+ query=query,
166
+ parameters=[{"name": "@limit", "value": limit}],
167
+ enable_cross_partition_query=True # Agregar este parámetro
168
+ ))
169
+
170
+ clean_sessions = []
171
+ for session in sessions:
172
+ try:
173
+ clean_sessions.append({
174
+ "username": session["username"],
175
+ "loginTime": session["loginTime"],
176
+ "logoutTime": session.get("logoutTime", "Activo"),
177
+ "sessionDuration": session.get("sessionDuration", 0)
178
+ })
179
+ except KeyError as e:
180
+ logger.warning(f"Sesión con datos incompletos: {e}")
181
+ continue
182
+
183
+ return clean_sessions
184
+ except Exception as e:
185
+ logger.error(f"Error obteniendo sesiones recientes: {str(e)}")
186
+ return []
187
+
188
+ #########################################
189
+ def get_user_total_time(username):
190
+ """Obtiene el tiempo total que un usuario ha pasado en la plataforma"""
191
+ try:
192
+ container = get_container("users_sessions")
193
+ if not container:
194
+ return None
195
+
196
+ query = """
197
+ SELECT VALUE SUM(c.sessionDuration)
198
+ FROM c
199
+ WHERE c.type = 'session'
200
+ AND c.username = @username
201
+ AND IS_DEFINED(c.sessionDuration)
202
+ """
203
+
204
+ result = list(container.query_items(
205
+ query=query,
206
+ parameters=[{"name": "@username", "value": username}]
207
+ ))
208
+
209
+ return result[0] if result and result[0] is not None else 0
210
+ except Exception as e:
211
+ logger.error(f"Error obteniendo tiempo total: {str(e)}")
212
+ return 0
213
+
214
+ #########################################
215
+ def update_student_user(username, new_info):
216
+ container = get_container("users")
217
+ try:
218
+ user = get_student_user(username)
219
+ if user:
220
+ user['additional_info'].update(new_info)
221
+ user['partitionKey'] = username
222
+ container.upsert_item(body=user)
223
+ logger.info(f"Información del estudiante actualizada: {username}")
224
+ return True
225
+ else:
226
+ logger.warning(f"Intento de actualizar estudiante no existente: {username}")
227
+ return False
228
+ except Exception as e:
229
+ logger.error(f"Error al actualizar información del estudiante {username}: {str(e)}")
230
+ return False
231
+
232
+ #########################################
233
+ def delete_student_user(username):
234
+ container = get_container("users")
235
+ try:
236
+ user = get_student_user(username)
237
+ if user:
238
+ # El ID es suficiente para eliminación ya que partitionKey está en el documento
239
+ container.delete_item(item=user['id'])
240
+ logger.info(f"Estudiante eliminado: {username}")
241
+ return True
242
+ else:
243
+ logger.warning(f"Intento de eliminar estudiante no existente: {username}")
244
+ return False
245
+ except Exception as e:
246
+ logger.error(f"Error al eliminar estudiante {username}: {str(e)}")
247
+ return False
248
+
249
+ #########################################
250
+ def store_application_request(name, lastname, email, institution, current_role, desired_role, reason):
251
+ """Almacena una solicitud de aplicación"""
252
+ try:
253
+ # Obtener el contenedor usando get_container() que sí funciona
254
+ container = get_container("application_requests")
255
+ if not container:
256
+ logger.error("No se pudo obtener el contenedor de solicitudes")
257
+ return False
258
+
259
+ # Crear documento con la solicitud
260
+ # Nótese que incluimos email como partition key en el cuerpo del documento
261
+ application_request = {
262
+ "id": str(uuid.uuid4()),
263
+ "name": name,
264
+ "lastname": lastname,
265
+ "email": email,
266
+ "institution": institution,
267
+ "current_role": current_role,
268
+ "desired_role": desired_role,
269
+ "reason": reason,
270
+ "requestDate": datetime.utcnow().isoformat(),
271
+ # El campo para partition key debe estar en el documento
272
+ "partitionKey": email
273
+ }
274
+
275
+ # Crear el item en el contenedor - sin el parámetro enable_cross_partition_query
276
+ container.create_item(
277
+ body=application_request # Solo pasamos el body
278
+ )
279
+ logger.info(f"Solicitud de aplicación almacenada para: {email}")
280
+ return True
281
+
282
+ except Exception as e:
283
+ logger.error(f"Error al almacenar la solicitud de aplicación: {str(e)}")
284
+ logger.error(f"Detalles del error: {str(e)}")
285
+ return False
286
+
287
+
288
+ ################################################################
289
+ def store_student_feedback(username, name, email, feedback):
290
+ """Almacena el feedback de un estudiante"""
291
+ try:
292
+ # Obtener el contenedor - verificar disponibilidad
293
+ logger.info(f"Intentando obtener contenedor user_feedback para usuario: {username}")
294
+ container = get_container("user_feedback")
295
+ if not container:
296
+ logger.error("No se pudo obtener el contenedor user_feedback")
297
+ return False
298
+
299
+ # Crear documento de feedback - asegurar que el username esté como partition key
300
+ feedback_item = {
301
+ "id": str(uuid.uuid4()),
302
+ "username": username, # Campo regular
303
+ "name": name,
304
+ "email": email,
305
+ "feedback": feedback,
306
+ "role": "Estudiante",
307
+ "timestamp": datetime.now(timezone.utc).isoformat(),
308
+ "partitionKey": username # Campo de partición
309
+ }
310
+
311
+ # Crear el item - sin el parámetro enable_cross_partition_query
312
+ logger.info(f"Intentando almacenar feedback para usuario: {username}")
313
+ result = container.create_item(
314
+ body=feedback_item # Solo el body, no parámetros adicionales
315
+ )
316
+
317
+ logger.info(f"Feedback almacenado exitosamente para el usuario: {username}")
318
+ return True
319
+
320
+ except Exception as e:
321
+ logger.error(f"Error al almacenar el feedback del estudiante {username}")
322
+ logger.error(f"Detalles del error: {str(e)}")
323
+ return False
modules/database/writing_progress_mongo_db.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/database/writing_progress_mongo_db.py
2
+
3
+ from .mongo_db import get_collection, insert_document
4
+ from datetime import datetime, timezone
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+ COLLECTION_NAME = 'writing_progress'
9
+
10
+ def store_writing_baseline(username, metrics, text):
11
+ """
12
+ Guarda la línea base de escritura de un usuario.
13
+ Args:
14
+ username: ID del usuario
15
+ metrics: Diccionario con métricas iniciales
16
+ text: Texto analizado
17
+ """
18
+ try:
19
+ document = {
20
+ 'username': username,
21
+ 'type': 'baseline',
22
+ 'metrics': metrics,
23
+ 'text': text,
24
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
25
+ 'iteration': 0 # Línea base siempre es iteración 0
26
+ }
27
+
28
+ # Verificar si ya existe una línea base
29
+ collection = get_collection(COLLECTION_NAME)
30
+ existing = collection.find_one({
31
+ 'username': username,
32
+ 'type': 'baseline'
33
+ })
34
+
35
+ if existing:
36
+ # Actualizar línea base existente
37
+ result = collection.update_one(
38
+ {'_id': existing['_id']},
39
+ {'$set': document}
40
+ )
41
+ success = result.modified_count > 0
42
+ else:
43
+ # Insertar nueva línea base
44
+ result = collection.insert_one(document)
45
+ success = result.inserted_id is not None
46
+
47
+ logger.info(f"Línea base {'actualizada' if existing else 'creada'} para usuario: {username}")
48
+ return success
49
+
50
+ except Exception as e:
51
+ logger.error(f"Error al guardar línea base: {str(e)}")
52
+ return False
53
+
54
+ def store_writing_progress(username, metrics, text):
55
+ """
56
+ Guarda una nueva iteración de progreso.
57
+ """
58
+ try:
59
+ # Obtener último número de iteración
60
+ collection = get_collection(COLLECTION_NAME)
61
+ last_progress = collection.find_one(
62
+ {'username': username},
63
+ sort=[('iteration', -1)]
64
+ )
65
+
66
+ next_iteration = (last_progress['iteration'] + 1) if last_progress else 1
67
+
68
+ document = {
69
+ 'username': username,
70
+ 'type': 'progress',
71
+ 'metrics': metrics,
72
+ 'text': text,
73
+ 'timestamp': datetime.now(timezone.utc).isoformat(),
74
+ 'iteration': next_iteration
75
+ }
76
+
77
+ result = collection.insert_one(document)
78
+ success = result.inserted_id is not None
79
+
80
+ if success:
81
+ logger.info(f"Progreso guardado para {username}, iteración {next_iteration}")
82
+
83
+ return success
84
+
85
+ except Exception as e:
86
+ logger.error(f"Error al guardar progreso: {str(e)}")
87
+ return False
88
+
89
+ def get_writing_baseline(username):
90
+ """
91
+ Obtiene la línea base de un usuario.
92
+ """
93
+ try:
94
+ collection = get_collection(COLLECTION_NAME)
95
+ return collection.find_one({
96
+ 'username': username,
97
+ 'type': 'baseline'
98
+ })
99
+ except Exception as e:
100
+ logger.error(f"Error al obtener línea base: {str(e)}")
101
+ return None
102
+
103
+ def get_writing_progress(username, limit=None):
104
+ """
105
+ Obtiene el historial de progreso de un usuario.
106
+ Args:
107
+ username: ID del usuario
108
+ limit: Número máximo de registros a retornar
109
+ """
110
+ try:
111
+ collection = get_collection(COLLECTION_NAME)
112
+ cursor = collection.find(
113
+ {
114
+ 'username': username,
115
+ 'type': 'progress'
116
+ },
117
+ sort=[('iteration', -1)]
118
+ )
119
+
120
+ if limit:
121
+ cursor = cursor.limit(limit)
122
+
123
+ return list(cursor)
124
+
125
+ except Exception as e:
126
+ logger.error(f"Error al obtener progreso: {str(e)}")
127
+ return []
128
+
129
+ def get_latest_writing_metrics(username):
130
+ """
131
+ Obtiene las métricas más recientes (línea base o progreso).
132
+ """
133
+ try:
134
+ collection = get_collection(COLLECTION_NAME)
135
+ return collection.find_one(
136
+ {'username': username},
137
+ sort=[('timestamp', -1)]
138
+ )
139
+ except Exception as e:
140
+ logger.error(f"Error al obtener métricas recientes: {str(e)}")
141
+ return None