AIdeaText commited on
Commit
bc7a943
·
verified ·
2 Parent(s): 0e55846 4361af6

Merge branch #AIdeaText/v3' into 'AIdeaText/v4'

Browse files
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 👀
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: streamlit
7
- sdk_version: 1.40.1
8
  app_file: app.py
9
  pinned: true
10
  license: mit
 
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: streamlit
7
+ sdk_version: 1.41.1
8
  app_file: app.py
9
  pinned: true
10
  license: mit
app.py CHANGED
@@ -109,6 +109,7 @@ from modules.database.chat_mongo_db import (
109
 
110
  # Importaciones de base de datos
111
  from modules.studentact.student_activities_v2 import display_student_activities
 
112
 
113
  from modules.auth.auth import (
114
  authenticate_student,
 
109
 
110
  # Importaciones de base de datos
111
  from modules.studentact.student_activities_v2 import display_student_activities
112
+ from modules.studentact.current_situation_interface import display_current_situation_interface
113
 
114
  from modules.auth.auth import (
115
  authenticate_student,
modules/__init__.py CHANGED
@@ -9,6 +9,69 @@ def load_auth_functions():
9
  'delete_student': delete_student
10
  }
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def load_database_functions():
13
 
14
  from .database.database_init import (
@@ -63,9 +126,23 @@ def load_database_functions():
63
  get_student_discourse_data
64
  )
65
 
 
 
 
 
 
 
 
 
 
66
  from .database.chat_mongo_db import store_chat_history, get_chat_history
67
 
68
  return {
 
 
 
 
 
69
  'initialize_database_connections': initialize_database_connections,
70
  'get_container': get_container,
71
  'get_mongodb': get_mongodb,
@@ -214,5 +291,6 @@ def load_all_functions():
214
  **load_discourse_functions(),
215
  **load_utils_functions(),
216
  **load_chatbot_functions(),
217
- **load_student_activities_functions()
 
218
  }
 
9
  'delete_student': delete_student
10
  }
11
 
12
+ # Agregar nuevo import para current_situation
13
+ def load_current_situation_functions():
14
+ """
15
+ Carga las funciones relacionadas con el análisis de situación actual.
16
+ Returns:
17
+ dict: Diccionario con las funciones de situación actual
18
+ """
19
+ from .studentact.current_situation_interface import (
20
+ display_current_situation_interface,
21
+ display_metrics_in_one_row,
22
+ display_empty_metrics_row,
23
+ display_metrics_analysis,
24
+ display_comparison_results,
25
+ display_metrics_and_suggestions,
26
+ display_radar_chart,
27
+ suggest_improvement_tools,
28
+ prepare_metrics_config
29
+ )
30
+
31
+ from .studentact.current_situation_analysis import (
32
+ correlate_metrics,
33
+ analyze_text_dimensions,
34
+ analyze_clarity,
35
+ analyze_vocabulary_diversity,
36
+ analyze_cohesion,
37
+ analyze_structure,
38
+ get_dependency_depths,
39
+ normalize_score,
40
+ generate_sentence_graphs,
41
+ generate_word_connections,
42
+ generate_connection_paths,
43
+ create_vocabulary_network,
44
+ create_syntax_complexity_graph,
45
+ create_cohesion_heatmap
46
+ )
47
+
48
+ return {
49
+ 'display_current_situation_interface': display_current_situation_interface,
50
+ 'display_metrics_in_one_row': display_metrics_in_one_line,
51
+ 'display_empty_metrics_row': display_empty_metrics_row,
52
+ 'display_metrics_analysis': display_metrics_analysis,
53
+ 'display_comparison_results': display_comparison_results,
54
+ 'display_metrics_and_suggestions': display_metrics_and_suggestions,
55
+ 'display_radar_chart': display_radar_chart,
56
+ 'suggest_improvement_tools': suggest_improvement_tools,
57
+ 'prepare_metrics_config': prepare_metrics_config,
58
+ 'display_empty_metrics_row' : display_empty_metrics_row,
59
+ 'correlate_metrics': correlate_metrics,
60
+ 'analyze_text_dimensions': analyze_text_dimensions,
61
+ 'analyze_clarity': analyze_clarity,
62
+ 'analyze_vocabulary_diversity': analyze_vocabulary_diversity,
63
+ 'analyze_cohesion': analyze_cohesion,
64
+ 'analyze_structure': analyze_structure,
65
+ 'get_dependency_depths': get_dependency_depths,
66
+ 'normalize_score': normalize_score,
67
+ 'generate_sentence_graphs': generate_sentence_graphs,
68
+ 'generate_word_connections': generate_word_connections,
69
+ 'generate_connection_paths': generate_connection_paths,
70
+ 'create_vocabulary_network': create_vocabulary_network,
71
+ 'create_syntax_complexity_graph': create_syntax_complexity_graph,
72
+ 'create_cohesion_heatmap': create_cohesion_heatmap
73
+ }
74
+
75
  def load_database_functions():
76
 
77
  from .database.database_init import (
 
126
  get_student_discourse_data
127
  )
128
 
129
+ # Agregar nueva importación para current_situation
130
+ from .database.current_situation_mongo_db import (
131
+ store_current_situation_result,
132
+ verify_storage,
133
+ get_recent_sessions,
134
+ get_student_situation_history,
135
+ update_exercise_status
136
+ )
137
+
138
  from .database.chat_mongo_db import store_chat_history, get_chat_history
139
 
140
  return {
141
+ 'store_current_situation_result': store_current_situation_result,
142
+ 'verify_storage': verify_storage,
143
+ 'get_recent_sessions': get_recent_sessions,
144
+ 'get_student_situation_history': get_student_situation_history,
145
+ 'update_exercise_status': update_exercise_status,
146
  'initialize_database_connections': initialize_database_connections,
147
  'get_container': get_container,
148
  'get_mongodb': get_mongodb,
 
291
  **load_discourse_functions(),
292
  **load_utils_functions(),
293
  **load_chatbot_functions(),
294
+ **load_student_activities_functions(),
295
+ **load_current_situation_functions() # Agregar el nuevo loader
296
  }
modules/database/current_situation_mongo_db.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_recent_situation_analysis(username, limit=5):
96
+ """
97
+ Obtiene los análisis más recientes de un usuario.
98
+ """
99
+ try:
100
+ collection = get_collection(COLLECTION_NAME)
101
+ if collection is None:
102
+ return []
103
+
104
+ results = collection.find(
105
+ {'username': username}
106
+ ).sort('timestamp', -1).limit(limit)
107
+
108
+ return list(results)
109
+
110
+ except Exception as e:
111
+ logger.error(f"Error obteniendo análisis recientes: {str(e)}")
112
+ return []
modules/database/mongo_db.py CHANGED
@@ -1,51 +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
- db = get_mongodb()
8
- return db[collection_name]
9
-
10
- def insert_document(collection_name, document):
11
- collection = get_collection(collection_name)
12
- try:
13
- result = collection.insert_one(document)
14
- logger.info(f"Documento insertado en {collection_name} con ID: {result.inserted_id}")
15
- return result.inserted_id
16
- except Exception as e:
17
- logger.error(f"Error al insertar documento en {collection_name}: {str(e)}")
18
- return None
19
-
20
- def find_documents(collection_name, query, sort=None, limit=None):
21
- collection = get_collection(collection_name)
22
- try:
23
- cursor = collection.find(query)
24
- if sort:
25
- cursor = cursor.sort(sort)
26
- if limit:
27
- cursor = cursor.limit(limit)
28
- return list(cursor)
29
- except Exception as e:
30
- logger.error(f"Error al buscar documentos en {collection_name}: {str(e)}")
31
- return []
32
-
33
- def update_document(collection_name, query, update):
34
- collection = get_collection(collection_name)
35
- try:
36
- result = collection.update_one(query, update)
37
- logger.info(f"Documento actualizado en {collection_name}: {result.modified_count} modificado(s)")
38
- return result.modified_count
39
- except Exception as e:
40
- logger.error(f"Error al actualizar documento en {collection_name}: {str(e)}")
41
- return 0
42
-
43
- def delete_document(collection_name, query):
44
- collection = get_collection(collection_name)
45
- try:
46
- result = collection.delete_one(query)
47
- logger.info(f"Documento eliminado de {collection_name}: {result.deleted_count} eliminado(s)")
48
- return result.deleted_count
49
- except Exception as e:
50
- logger.error(f"Error al eliminar documento de {collection_name}: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
51
  return 0
 
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/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
modules/morphosyntax/morphosyntax_interface-BackUp_Dec24_OK.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #modules/morphosyntax/morphosyntax_interface.py
2
+ import streamlit as st
3
+ from streamlit_float import *
4
+ from streamlit_antd_components import *
5
+ from streamlit.components.v1 import html
6
+ import spacy
7
+ from spacy import displacy
8
+ import spacy_streamlit
9
+ import pandas as pd
10
+ import base64
11
+ import re
12
+
13
+ # Importar desde morphosyntax_process.py
14
+ from .morphosyntax_process import (
15
+ process_morphosyntactic_input,
16
+ format_analysis_results,
17
+ perform_advanced_morphosyntactic_analysis, # Añadir esta importación
18
+ get_repeated_words_colors, # Y estas también
19
+ highlight_repeated_words,
20
+ POS_COLORS,
21
+ POS_TRANSLATIONS
22
+ )
23
+
24
+ from ..utils.widget_utils import generate_unique_key
25
+
26
+ from ..database.morphosintax_mongo_db import store_student_morphosyntax_result
27
+ from ..database.chat_mongo_db import store_chat_history, get_chat_history
28
+
29
+ # from ..database.morphosintaxis_export import export_user_interactions
30
+
31
+ import logging
32
+ logger = logging.getLogger(__name__)
33
+
34
+ ############################################################################################################
35
+ def display_morphosyntax_interface(lang_code, nlp_models, morpho_t):
36
+ try:
37
+ # 1. Inicializar el estado morfosintáctico si no existe
38
+ if 'morphosyntax_state' not in st.session_state:
39
+ st.session_state.morphosyntax_state = {
40
+ 'input_text': "",
41
+ 'analysis_count': 0,
42
+ 'last_analysis': None
43
+ }
44
+
45
+ # 2. Campo de entrada de texto con key única basada en el contador
46
+ input_key = f"morpho_input_{st.session_state.morphosyntax_state['analysis_count']}"
47
+
48
+ sentence_input = st.text_area(
49
+ morpho_t.get('morpho_input_label', 'Enter text to analyze'),
50
+ height=150,
51
+ placeholder=morpho_t.get('morpho_input_placeholder', 'Enter your text here...'),
52
+ key=input_key
53
+ )
54
+
55
+ # 3. Actualizar el estado con el texto actual
56
+ st.session_state.morphosyntax_state['input_text'] = sentence_input
57
+
58
+ # 4. Crear columnas para el botón
59
+ col1, col2, col3 = st.columns([2,1,2])
60
+
61
+ # 5. Botón de análisis en la columna central
62
+ with col1:
63
+ analyze_button = st.button(
64
+ morpho_t.get('morpho_analyze_button', 'Analyze Morphosyntax'),
65
+ key=f"morpho_button_{st.session_state.morphosyntax_state['analysis_count']}",
66
+ type="primary", # Nuevo en Streamlit 1.39.0
67
+ icon="🔍", # Nuevo en Streamlit 1.39.0
68
+ disabled=not bool(sentence_input.strip()), # Se activa solo cuando hay texto
69
+ use_container_width=True
70
+ )
71
+
72
+ # 6. Lógica de análisis
73
+ if analyze_button and sentence_input.strip(): # Verificar que haya texto y no solo espacios
74
+ try:
75
+ with st.spinner(morpho_t.get('processing', 'Processing...')):
76
+ # Obtener el modelo específico del idioma y procesar el texto
77
+ doc = nlp_models[lang_code](sentence_input)
78
+
79
+ # Realizar análisis morfosintáctico con el mismo modelo
80
+ advanced_analysis = perform_advanced_morphosyntactic_analysis(
81
+ sentence_input,
82
+ nlp_models[lang_code]
83
+ )
84
+
85
+ # Guardar resultado en el estado de la sesión
86
+ st.session_state.morphosyntax_result = {
87
+ 'doc': doc,
88
+ 'advanced_analysis': advanced_analysis
89
+ }
90
+
91
+ # Incrementar el contador de análisis
92
+ st.session_state.morphosyntax_state['analysis_count'] += 1
93
+
94
+ # Guardar el análisis en la base de datos
95
+ if store_student_morphosyntax_result(
96
+ username=st.session_state.username,
97
+ text=sentence_input,
98
+ arc_diagrams=advanced_analysis['arc_diagrams']
99
+ ):
100
+ st.success(morpho_t.get('success_message', 'Analysis saved successfully'))
101
+
102
+ # Mostrar resultados
103
+ display_morphosyntax_results(
104
+ st.session_state.morphosyntax_result,
105
+ lang_code,
106
+ morpho_t
107
+ )
108
+ else:
109
+ st.error(morpho_t.get('error_message', 'Error saving analysis'))
110
+
111
+ except Exception as e:
112
+ logger.error(f"Error en análisis morfosintáctico: {str(e)}")
113
+ st.error(morpho_t.get('error_processing', f'Error processing text: {str(e)}'))
114
+
115
+ # 7. Mostrar resultados previos si existen
116
+ elif 'morphosyntax_result' in st.session_state and st.session_state.morphosyntax_result is not None:
117
+ display_morphosyntax_results(
118
+ st.session_state.morphosyntax_result,
119
+ lang_code,
120
+ morpho_t
121
+ )
122
+ elif not sentence_input.strip():
123
+ st.info(morpho_t.get('morpho_initial_message', 'Enter text to begin analysis'))
124
+
125
+ except Exception as e:
126
+ logger.error(f"Error general en display_morphosyntax_interface: {str(e)}")
127
+ st.error("Se produjo un error. Por favor, intente de nuevo.")
128
+ st.error(f"Detalles del error: {str(e)}") # Añadido para mejor debugging
129
+
130
+ ############################################################################################################
131
+ def display_morphosyntax_results(result, lang_code, morpho_t):
132
+ """
133
+ Muestra los resultados del análisis morfosintáctico.
134
+ Args:
135
+ result: Resultado del análisis
136
+ lang_code: Código del idioma
137
+ t: Diccionario de traducciones
138
+ """
139
+ # Obtener el diccionario de traducciones morfosintácticas
140
+ # morpho_t = t.get('MORPHOSYNTACTIC', {})
141
+
142
+ if result is None:
143
+ st.warning(morpho_t.get('no_results', 'No results available'))
144
+ return
145
+
146
+ doc = result['doc']
147
+ advanced_analysis = result['advanced_analysis']
148
+
149
+ # Mostrar leyenda
150
+ st.markdown(f"##### {morpho_t.get('legend', 'Legend: Grammatical categories')}")
151
+ legend_html = "<div style='display: flex; flex-wrap: wrap;'>"
152
+ for pos, color in POS_COLORS.items():
153
+ if pos in POS_TRANSLATIONS[lang_code]:
154
+ legend_html += f"<div style='margin-right: 10px;'><span style='background-color: {color}; padding: 2px 5px;'>{POS_TRANSLATIONS[lang_code][pos]}</span></div>"
155
+ legend_html += "</div>"
156
+ st.markdown(legend_html, unsafe_allow_html=True)
157
+
158
+ # Mostrar análisis de palabras repetidas
159
+ word_colors = get_repeated_words_colors(doc)
160
+ with st.expander(morpho_t.get('repeated_words', 'Repeated words'), expanded=True):
161
+ highlighted_text = highlight_repeated_words(doc, word_colors)
162
+ st.markdown(highlighted_text, unsafe_allow_html=True)
163
+
164
+ # Mostrar estructura de oraciones
165
+ with st.expander(morpho_t.get('sentence_structure', 'Sentence structure'), expanded=True):
166
+ for i, sent_analysis in enumerate(advanced_analysis['sentence_structure']):
167
+ sentence_str = (
168
+ f"**{morpho_t.get('sentence', 'Sentence')} {i+1}** " # Aquí está el cambio
169
+ f"{morpho_t.get('root', 'Root')}: {sent_analysis['root']} ({sent_analysis['root_pos']}) -- " # Y aquí
170
+ f"{morpho_t.get('subjects', 'Subjects')}: {', '.join(sent_analysis['subjects'])} -- " # Y aquí
171
+ f"{morpho_t.get('objects', 'Objects')}: {', '.join(sent_analysis['objects'])} -- " # Y aquí
172
+ f"{morpho_t.get('verbs', 'Verbs')}: {', '.join(sent_analysis['verbs'])}" # Y aquí
173
+ )
174
+ st.markdown(sentence_str)
175
+
176
+ # Mostrar análisis de categorías gramaticales # Mostrar análisis morfológico
177
+ col1, col2 = st.columns(2)
178
+
179
+ with col1:
180
+ with st.expander(morpho_t.get('pos_analysis', 'Part of speech'), expanded=True):
181
+ pos_df = pd.DataFrame(advanced_analysis['pos_analysis'])
182
+
183
+ # Traducir las etiquetas POS a sus nombres en el idioma seleccionado
184
+ pos_df['pos'] = pos_df['pos'].map(lambda x: POS_TRANSLATIONS[lang_code].get(x, x))
185
+
186
+ # Renombrar las columnas para mayor claridad
187
+ pos_df = pos_df.rename(columns={
188
+ 'pos': morpho_t.get('grammatical_category', 'Grammatical category'),
189
+ 'count': morpho_t.get('count', 'Count'),
190
+ 'percentage': morpho_t.get('percentage', 'Percentage'),
191
+ 'examples': morpho_t.get('examples', 'Examples')
192
+ })
193
+
194
+ # Mostrar el dataframe
195
+ st.dataframe(pos_df)
196
+
197
+ with col2:
198
+ with st.expander(morpho_t.get('morphological_analysis', 'Morphological Analysis'), expanded=True):
199
+ # 1. Crear el DataFrame inicial
200
+ morph_df = pd.DataFrame(advanced_analysis['morphological_analysis'])
201
+
202
+ # 2. Primero renombrar las columnas usando las traducciones de la interfaz
203
+ column_mapping = {
204
+ 'text': morpho_t.get('word', 'Word'),
205
+ 'lemma': morpho_t.get('lemma', 'Lemma'),
206
+ 'pos': morpho_t.get('grammatical_category', 'Grammatical category'),
207
+ 'dep': morpho_t.get('dependency', 'Dependency'),
208
+ 'morph': morpho_t.get('morphology', 'Morphology')
209
+ }
210
+
211
+ # 3. Aplicar el renombrado
212
+ morph_df = morph_df.rename(columns=column_mapping)
213
+
214
+ # 4. Traducir las categorías gramaticales usando POS_TRANSLATIONS global
215
+ grammatical_category = morpho_t.get('grammatical_category', 'Grammatical category')
216
+ morph_df[grammatical_category] = morph_df[grammatical_category].map(lambda x: POS_TRANSLATIONS[lang_code].get(x, x))
217
+
218
+ # 2.2 Traducir dependencias usando traducciones específicas
219
+ dep_translations = {
220
+
221
+ 'es': {
222
+ 'ROOT': 'RAÍZ', 'nsubj': 'sujeto nominal', 'obj': 'objeto', 'iobj': 'objeto indirecto',
223
+ 'csubj': 'sujeto clausal', 'ccomp': 'complemento clausal', 'xcomp': 'complemento clausal abierto',
224
+ 'obl': 'oblicuo', 'vocative': 'vocativo', 'expl': 'expletivo', 'dislocated': 'dislocado',
225
+ 'advcl': 'cláusula adverbial', 'advmod': 'modificador adverbial', 'discourse': 'discurso',
226
+ 'aux': 'auxiliar', 'cop': 'cópula', 'mark': 'marcador', 'nmod': 'modificador nominal',
227
+ 'appos': 'aposición', 'nummod': 'modificador numeral', 'acl': 'cláusula adjetiva',
228
+ 'amod': 'modificador adjetival', 'det': 'determinante', 'clf': 'clasificador',
229
+ 'case': 'caso', 'conj': 'conjunción', 'cc': 'coordinante', 'fixed': 'fijo',
230
+ 'flat': 'plano', 'compound': 'compuesto', 'list': 'lista', 'parataxis': 'parataxis',
231
+ 'orphan': 'huérfano', 'goeswith': 'va con', 'reparandum': 'reparación', 'punct': 'puntuación'
232
+ },
233
+
234
+ 'en': {
235
+ 'ROOT': 'ROOT', 'nsubj': 'nominal subject', 'obj': 'object',
236
+ 'iobj': 'indirect object', 'csubj': 'clausal subject', 'ccomp': 'clausal complement', 'xcomp': 'open clausal complement',
237
+ 'obl': 'oblique', 'vocative': 'vocative', 'expl': 'expletive', 'dislocated': 'dislocated', 'advcl': 'adverbial clause modifier',
238
+ 'advmod': 'adverbial modifier', 'discourse': 'discourse element', 'aux': 'auxiliary', 'cop': 'copula', 'mark': 'marker',
239
+ 'nmod': 'nominal modifier', 'appos': 'appositional modifier', 'nummod': 'numeric modifier', 'acl': 'clausal modifier of noun',
240
+ 'amod': 'adjectival modifier', 'det': 'determiner', 'clf': 'classifier', 'case': 'case marking',
241
+ 'conj': 'conjunct', 'cc': 'coordinating conjunction', 'fixed': 'fixed multiword expression',
242
+ 'flat': 'flat multiword expression', 'compound': 'compound', 'list': 'list', 'parataxis': 'parataxis', 'orphan': 'orphan',
243
+ 'goeswith': 'goes with', 'reparandum': 'reparandum', 'punct': 'punctuation'
244
+ },
245
+
246
+ 'fr': {
247
+ 'ROOT': 'RACINE', 'nsubj': 'sujet nominal', 'obj': 'objet', 'iobj': 'objet indirect',
248
+ 'csubj': 'sujet phrastique', 'ccomp': 'complément phrastique', 'xcomp': 'complément phrastique ouvert', 'obl': 'oblique',
249
+ 'vocative': 'vocatif', 'expl': 'explétif', 'dislocated': 'disloqué', 'advcl': 'clause adverbiale', 'advmod': 'modifieur adverbial',
250
+ 'discourse': 'élément de discours', 'aux': 'auxiliaire', 'cop': 'copule', 'mark': 'marqueur', 'nmod': 'modifieur nominal',
251
+ 'appos': 'apposition', 'nummod': 'modifieur numéral', 'acl': 'clause relative', 'amod': 'modifieur adjectival', 'det': 'déterminant',
252
+ 'clf': 'classificateur', 'case': 'marqueur de cas', 'conj': 'conjonction', 'cc': 'coordination', 'fixed': 'expression figée',
253
+ 'flat': 'construction plate', 'compound': 'composé', 'list': 'liste', 'parataxis': 'parataxe', 'orphan': 'orphelin',
254
+ 'goeswith': 'va avec', 'reparandum': 'réparation', 'punct': 'ponctuation'
255
+ }
256
+ }
257
+
258
+ dependency = morpho_t.get('dependency', 'Dependency')
259
+ morph_df[dependency] = morph_df[dependency].map(lambda x: dep_translations[lang_code].get(x, x))
260
+
261
+ morph_translations = {
262
+ 'es': {
263
+ 'Gender': 'Género', 'Number': 'Número', 'Case': 'Caso', 'Definite': 'Definido',
264
+ 'PronType': 'Tipo de Pronombre', 'Person': 'Persona', 'Mood': 'Modo',
265
+ 'Tense': 'Tiempo', 'VerbForm': 'Forma Verbal', 'Voice': 'Voz',
266
+ 'Fem': 'Femenino', 'Masc': 'Masculino', 'Sing': 'Singular', 'Plur': 'Plural',
267
+ 'Ind': 'Indicativo', 'Sub': 'Subjuntivo', 'Imp': 'Imperativo', 'Inf': 'Infinitivo',
268
+ 'Part': 'Participio', 'Ger': 'Gerundio', 'Pres': 'Presente', 'Past': 'Pasado',
269
+ 'Fut': 'Futuro', 'Perf': 'Perfecto', 'Imp': 'Imperfecto'
270
+ },
271
+
272
+ 'en': {
273
+ 'Gender': 'Gender', 'Number': 'Number', 'Case': 'Case', 'Definite': 'Definite', 'PronType': 'Pronoun Type', 'Person': 'Person',
274
+ 'Mood': 'Mood', 'Tense': 'Tense', 'VerbForm': 'Verb Form', 'Voice': 'Voice',
275
+ 'Fem': 'Feminine', 'Masc': 'Masculine', 'Sing': 'Singular', 'Plur': 'Plural', 'Ind': 'Indicative',
276
+ 'Sub': 'Subjunctive', 'Imp': 'Imperative', 'Inf': 'Infinitive', 'Part': 'Participle',
277
+ 'Ger': 'Gerund', 'Pres': 'Present', 'Past': 'Past', 'Fut': 'Future', 'Perf': 'Perfect', 'Imp': 'Imperfect'
278
+ },
279
+
280
+ 'fr': {
281
+ 'Gender': 'Genre', 'Number': 'Nombre', 'Case': 'Cas', 'Definite': 'Défini', 'PronType': 'Type de Pronom',
282
+ 'Person': 'Personne', 'Mood': 'Mode', 'Tense': 'Temps', 'VerbForm': 'Forme Verbale', 'Voice': 'Voix',
283
+ 'Fem': 'Féminin', 'Masc': 'Masculin', 'Sing': 'Singulier', 'Plur': 'Pluriel', 'Ind': 'Indicatif',
284
+ 'Sub': 'Subjonctif', 'Imp': 'Impératif', 'Inf': 'Infinitif', 'Part': 'Participe',
285
+ 'Ger': 'Gérondif', 'Pres': 'Présent', 'Past': 'Passé', 'Fut': 'Futur', 'Perf': 'Parfait', 'Imp': 'Imparfait'
286
+ }
287
+ }
288
+
289
+ def translate_morph(morph_string, lang_code):
290
+ for key, value in morph_translations[lang_code].items():
291
+ morph_string = morph_string.replace(key, value)
292
+ return morph_string
293
+
294
+ morphology = morpho_t.get('morphology', 'Morphology')
295
+ morph_df[morphology] = morph_df[morphology].apply(lambda x: translate_morph(x, lang_code))
296
+
297
+ st.dataframe(morph_df)
298
+
299
+ # Mostrar diagramas de arco
300
+ with st.expander(morpho_t.get('arc_diagram', 'Syntactic analysis: Arc diagram'), expanded=True):
301
+ sentences = list(doc.sents)
302
+ arc_diagrams = []
303
+
304
+ for i, sent in enumerate(sentences):
305
+ st.subheader(f"{morpho_t.get('sentence', 'Sentence')} {i+1}")
306
+ html = displacy.render(sent, style="dep", options={"distance": 100})
307
+ html = html.replace('height="375"', 'height="200"')
308
+ html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html)
309
+ html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"',
310
+ lambda m: f'<g transform="translate({m.group(1)},50)"', html)
311
+ st.write(html, unsafe_allow_html=True)
312
+ arc_diagrams.append(html)
313
+
314
+ # Botón de exportación
315
+ # if st.button(morpho_t.get('export_button', 'Export Analysis')):
316
+ # pdf_buffer = export_user_interactions(st.session_state.username, 'morphosyntax')
317
+ # st.download_button(
318
+ # label=morpho_t.get('download_pdf', 'Download PDF'),
319
+ # data=pdf_buffer,
320
+ # file_name="morphosyntax_analysis.pdf",
321
+ # mime="application/pdf"
322
+ # )
modules/morphosyntax/morphosyntax_interface.py CHANGED
@@ -1,4 +1,5 @@
1
  #modules/morphosyntax/morphosyntax_interface.py
 
2
  import streamlit as st
3
  from streamlit_float import *
4
  from streamlit_antd_components import *
@@ -10,313 +11,298 @@ import pandas as pd
10
  import base64
11
  import re
12
 
13
- # Importar desde morphosyntax_process.py
14
  from .morphosyntax_process import (
15
  process_morphosyntactic_input,
16
  format_analysis_results,
17
- perform_advanced_morphosyntactic_analysis, # Añadir esta importación
18
- get_repeated_words_colors, # Y estas también
19
  highlight_repeated_words,
20
  POS_COLORS,
21
  POS_TRANSLATIONS
22
  )
23
 
24
  from ..utils.widget_utils import generate_unique_key
25
-
26
  from ..database.morphosintax_mongo_db import store_student_morphosyntax_result
27
  from ..database.chat_mongo_db import store_chat_history, get_chat_history
28
 
29
- # from ..database.morphosintaxis_export import export_user_interactions
30
-
31
  import logging
32
  logger = logging.getLogger(__name__)
33
 
34
- ############################################################################################################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  def display_morphosyntax_interface(lang_code, nlp_models, morpho_t):
36
  try:
37
- # 1. Inicializar el estado morfosintáctico si no existe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if 'morphosyntax_state' not in st.session_state:
39
  st.session_state.morphosyntax_state = {
40
- 'input_text': "",
 
 
41
  'analysis_count': 0,
42
- 'last_analysis': None
43
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- # 2. Campo de entrada de texto con key única basada en el contador
46
- input_key = f"morpho_input_{st.session_state.morphosyntax_state['analysis_count']}"
47
-
48
- sentence_input = st.text_area(
49
- morpho_t.get('morpho_input_label', 'Enter text to analyze'),
50
- height=150,
51
- placeholder=morpho_t.get('morpho_input_placeholder', 'Enter your text here...'),
52
- key=input_key
53
- )
54
 
55
- # 3. Actualizar el estado con el texto actual
56
- st.session_state.morphosyntax_state['input_text'] = sentence_input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- # 4. Crear columnas para el botón
59
- col1, col2, col3 = st.columns([2,1,2])
60
-
61
- # 5. Botón de análisis en la columna central
62
- with col1:
63
- analyze_button = st.button(
64
- morpho_t.get('morpho_analyze_button', 'Analyze Morphosyntax'),
65
- key=f"morpho_button_{st.session_state.morphosyntax_state['analysis_count']}",
66
- type="primary", # Nuevo en Streamlit 1.39.0
67
- icon="🔍", # Nuevo en Streamlit 1.39.0
68
- disabled=not bool(sentence_input.strip()), # Se activa solo cuando hay texto
69
- use_container_width=True
 
 
 
 
 
 
 
70
  )
71
 
72
- # 6. Lógica de análisis
73
- if analyze_button and sentence_input.strip(): # Verificar que haya texto y no solo espacios
74
- try:
75
- with st.spinner(morpho_t.get('processing', 'Processing...')):
76
- # Obtener el modelo específico del idioma y procesar el texto
77
- doc = nlp_models[lang_code](sentence_input)
78
-
79
- # Realizar análisis morfosintáctico con el mismo modelo
80
- advanced_analysis = perform_advanced_morphosyntactic_analysis(
81
- sentence_input,
82
- nlp_models[lang_code]
83
- )
84
-
85
- # Guardar resultado en el estado de la sesión
86
- st.session_state.morphosyntax_result = {
87
- 'doc': doc,
88
- 'advanced_analysis': advanced_analysis
89
- }
90
-
91
- # Incrementar el contador de análisis
92
- st.session_state.morphosyntax_state['analysis_count'] += 1
93
-
94
- # Guardar el análisis en la base de datos
95
- if store_student_morphosyntax_result(
96
- username=st.session_state.username,
97
- text=sentence_input,
98
- arc_diagrams=advanced_analysis['arc_diagrams']
99
- ):
100
- st.success(morpho_t.get('success_message', 'Analysis saved successfully'))
101
 
102
- # Mostrar resultados
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  display_morphosyntax_results(
104
- st.session_state.morphosyntax_result,
105
- lang_code,
106
  morpho_t
107
  )
108
- else:
109
- st.error(morpho_t.get('error_message', 'Error saving analysis'))
110
-
111
- except Exception as e:
112
- logger.error(f"Error en análisis morfosintáctico: {str(e)}")
113
- st.error(morpho_t.get('error_processing', f'Error processing text: {str(e)}'))
114
-
115
- # 7. Mostrar resultados previos si existen
116
- elif 'morphosyntax_result' in st.session_state and st.session_state.morphosyntax_result is not None:
117
- display_morphosyntax_results(
118
- st.session_state.morphosyntax_result,
119
- lang_code,
120
- morpho_t
121
- )
122
- elif not sentence_input.strip():
123
- st.info(morpho_t.get('morpho_initial_message', 'Enter text to begin analysis'))
124
-
125
  except Exception as e:
126
  logger.error(f"Error general en display_morphosyntax_interface: {str(e)}")
127
  st.error("Se produjo un error. Por favor, intente de nuevo.")
128
- st.error(f"Detalles del error: {str(e)}") # Añadido para mejor debugging
129
 
130
- ############################################################################################################
 
 
 
 
131
  def display_morphosyntax_results(result, lang_code, morpho_t):
132
  """
133
- Muestra los resultados del análisis morfosintáctico.
134
- Args:
135
- result: Resultado del análisis
136
- lang_code: Código del idioma
137
- t: Diccionario de traducciones
138
  """
139
- # Obtener el diccionario de traducciones morfosintácticas
140
- # morpho_t = t.get('MORPHOSYNTACTIC', {})
141
-
142
  if result is None:
143
  st.warning(morpho_t.get('no_results', 'No results available'))
144
  return
145
 
146
  doc = result['doc']
147
- advanced_analysis = result['advanced_analysis']
148
-
149
- # Mostrar leyenda
150
- st.markdown(f"##### {morpho_t.get('legend', 'Legend: Grammatical categories')}")
151
- legend_html = "<div style='display: flex; flex-wrap: wrap;'>"
152
- for pos, color in POS_COLORS.items():
153
- if pos in POS_TRANSLATIONS[lang_code]:
154
- legend_html += f"<div style='margin-right: 10px;'><span style='background-color: {color}; padding: 2px 5px;'>{POS_TRANSLATIONS[lang_code][pos]}</span></div>"
155
- legend_html += "</div>"
156
- st.markdown(legend_html, unsafe_allow_html=True)
157
 
158
- # Mostrar análisis de palabras repetidas
159
- word_colors = get_repeated_words_colors(doc)
160
- with st.expander(morpho_t.get('repeated_words', 'Repeated words'), expanded=True):
161
- highlighted_text = highlight_repeated_words(doc, word_colors)
162
- st.markdown(highlighted_text, unsafe_allow_html=True)
163
 
164
- # Mostrar estructura de oraciones
165
- with st.expander(morpho_t.get('sentence_structure', 'Sentence structure'), expanded=True):
166
- for i, sent_analysis in enumerate(advanced_analysis['sentence_structure']):
167
- sentence_str = (
168
- f"**{morpho_t.get('sentence', 'Sentence')} {i+1}** " # Aquí está el cambio
169
- f"{morpho_t.get('root', 'Root')}: {sent_analysis['root']} ({sent_analysis['root_pos']}) -- " # Y aquí
170
- f"{morpho_t.get('subjects', 'Subjects')}: {', '.join(sent_analysis['subjects'])} -- " # Y aquí
171
- f"{morpho_t.get('objects', 'Objects')}: {', '.join(sent_analysis['objects'])} -- " # Y aquí
172
- f"{morpho_t.get('verbs', 'Verbs')}: {', '.join(sent_analysis['verbs'])}" # Y aquí
173
- )
174
- st.markdown(sentence_str)
175
-
176
- # Mostrar análisis de categorías gramaticales # Mostrar análisis morfológico
177
- col1, col2 = st.columns(2)
178
-
179
- with col1:
180
- with st.expander(morpho_t.get('pos_analysis', 'Part of speech'), expanded=True):
181
- pos_df = pd.DataFrame(advanced_analysis['pos_analysis'])
182
-
183
- # Traducir las etiquetas POS a sus nombres en el idioma seleccionado
184
- pos_df['pos'] = pos_df['pos'].map(lambda x: POS_TRANSLATIONS[lang_code].get(x, x))
185
-
186
- # Renombrar las columnas para mayor claridad
187
- pos_df = pos_df.rename(columns={
188
- 'pos': morpho_t.get('grammatical_category', 'Grammatical category'),
189
- 'count': morpho_t.get('count', 'Count'),
190
- 'percentage': morpho_t.get('percentage', 'Percentage'),
191
- 'examples': morpho_t.get('examples', 'Examples')
192
- })
193
-
194
- # Mostrar el dataframe
195
- st.dataframe(pos_df)
196
-
197
- with col2:
198
- with st.expander(morpho_t.get('morphological_analysis', 'Morphological Analysis'), expanded=True):
199
- # 1. Crear el DataFrame inicial
200
- morph_df = pd.DataFrame(advanced_analysis['morphological_analysis'])
201
-
202
- # 2. Primero renombrar las columnas usando las traducciones de la interfaz
203
- column_mapping = {
204
- 'text': morpho_t.get('word', 'Word'),
205
- 'lemma': morpho_t.get('lemma', 'Lemma'),
206
- 'pos': morpho_t.get('grammatical_category', 'Grammatical category'),
207
- 'dep': morpho_t.get('dependency', 'Dependency'),
208
- 'morph': morpho_t.get('morphology', 'Morphology')
209
- }
210
-
211
- # 3. Aplicar el renombrado
212
- morph_df = morph_df.rename(columns=column_mapping)
213
-
214
- # 4. Traducir las categorías gramaticales usando POS_TRANSLATIONS global
215
- grammatical_category = morpho_t.get('grammatical_category', 'Grammatical category')
216
- morph_df[grammatical_category] = morph_df[grammatical_category].map(lambda x: POS_TRANSLATIONS[lang_code].get(x, x))
217
-
218
- # 2.2 Traducir dependencias usando traducciones específicas
219
- dep_translations = {
220
-
221
- 'es': {
222
- 'ROOT': 'RAÍZ', 'nsubj': 'sujeto nominal', 'obj': 'objeto', 'iobj': 'objeto indirecto',
223
- 'csubj': 'sujeto clausal', 'ccomp': 'complemento clausal', 'xcomp': 'complemento clausal abierto',
224
- 'obl': 'oblicuo', 'vocative': 'vocativo', 'expl': 'expletivo', 'dislocated': 'dislocado',
225
- 'advcl': 'cláusula adverbial', 'advmod': 'modificador adverbial', 'discourse': 'discurso',
226
- 'aux': 'auxiliar', 'cop': 'cópula', 'mark': 'marcador', 'nmod': 'modificador nominal',
227
- 'appos': 'aposición', 'nummod': 'modificador numeral', 'acl': 'cláusula adjetiva',
228
- 'amod': 'modificador adjetival', 'det': 'determinante', 'clf': 'clasificador',
229
- 'case': 'caso', 'conj': 'conjunción', 'cc': 'coordinante', 'fixed': 'fijo',
230
- 'flat': 'plano', 'compound': 'compuesto', 'list': 'lista', 'parataxis': 'parataxis',
231
- 'orphan': 'huérfano', 'goeswith': 'va con', 'reparandum': 'reparación', 'punct': 'puntuación'
232
- },
233
-
234
- 'en': {
235
- 'ROOT': 'ROOT', 'nsubj': 'nominal subject', 'obj': 'object',
236
- 'iobj': 'indirect object', 'csubj': 'clausal subject', 'ccomp': 'clausal complement', 'xcomp': 'open clausal complement',
237
- 'obl': 'oblique', 'vocative': 'vocative', 'expl': 'expletive', 'dislocated': 'dislocated', 'advcl': 'adverbial clause modifier',
238
- 'advmod': 'adverbial modifier', 'discourse': 'discourse element', 'aux': 'auxiliary', 'cop': 'copula', 'mark': 'marker',
239
- 'nmod': 'nominal modifier', 'appos': 'appositional modifier', 'nummod': 'numeric modifier', 'acl': 'clausal modifier of noun',
240
- 'amod': 'adjectival modifier', 'det': 'determiner', 'clf': 'classifier', 'case': 'case marking',
241
- 'conj': 'conjunct', 'cc': 'coordinating conjunction', 'fixed': 'fixed multiword expression',
242
- 'flat': 'flat multiword expression', 'compound': 'compound', 'list': 'list', 'parataxis': 'parataxis', 'orphan': 'orphan',
243
- 'goeswith': 'goes with', 'reparandum': 'reparandum', 'punct': 'punctuation'
244
- },
245
-
246
- 'fr': {
247
- 'ROOT': 'RACINE', 'nsubj': 'sujet nominal', 'obj': 'objet', 'iobj': 'objet indirect',
248
- 'csubj': 'sujet phrastique', 'ccomp': 'complément phrastique', 'xcomp': 'complément phrastique ouvert', 'obl': 'oblique',
249
- 'vocative': 'vocatif', 'expl': 'explétif', 'dislocated': 'disloqué', 'advcl': 'clause adverbiale', 'advmod': 'modifieur adverbial',
250
- 'discourse': 'élément de discours', 'aux': 'auxiliaire', 'cop': 'copule', 'mark': 'marqueur', 'nmod': 'modifieur nominal',
251
- 'appos': 'apposition', 'nummod': 'modifieur numéral', 'acl': 'clause relative', 'amod': 'modifieur adjectival', 'det': 'déterminant',
252
- 'clf': 'classificateur', 'case': 'marqueur de cas', 'conj': 'conjonction', 'cc': 'coordination', 'fixed': 'expression figée',
253
- 'flat': 'construction plate', 'compound': 'composé', 'list': 'liste', 'parataxis': 'parataxe', 'orphan': 'orphelin',
254
- 'goeswith': 'va avec', 'reparandum': 'réparation', 'punct': 'ponctuation'
255
- }
256
- }
257
-
258
- dependency = morpho_t.get('dependency', 'Dependency')
259
- morph_df[dependency] = morph_df[dependency].map(lambda x: dep_translations[lang_code].get(x, x))
260
-
261
- morph_translations = {
262
- 'es': {
263
- 'Gender': 'Género', 'Number': 'Número', 'Case': 'Caso', 'Definite': 'Definido',
264
- 'PronType': 'Tipo de Pronombre', 'Person': 'Persona', 'Mood': 'Modo',
265
- 'Tense': 'Tiempo', 'VerbForm': 'Forma Verbal', 'Voice': 'Voz',
266
- 'Fem': 'Femenino', 'Masc': 'Masculino', 'Sing': 'Singular', 'Plur': 'Plural',
267
- 'Ind': 'Indicativo', 'Sub': 'Subjuntivo', 'Imp': 'Imperativo', 'Inf': 'Infinitivo',
268
- 'Part': 'Participio', 'Ger': 'Gerundio', 'Pres': 'Presente', 'Past': 'Pasado',
269
- 'Fut': 'Futuro', 'Perf': 'Perfecto', 'Imp': 'Imperfecto'
270
- },
271
-
272
- 'en': {
273
- 'Gender': 'Gender', 'Number': 'Number', 'Case': 'Case', 'Definite': 'Definite', 'PronType': 'Pronoun Type', 'Person': 'Person',
274
- 'Mood': 'Mood', 'Tense': 'Tense', 'VerbForm': 'Verb Form', 'Voice': 'Voice',
275
- 'Fem': 'Feminine', 'Masc': 'Masculine', 'Sing': 'Singular', 'Plur': 'Plural', 'Ind': 'Indicative',
276
- 'Sub': 'Subjunctive', 'Imp': 'Imperative', 'Inf': 'Infinitive', 'Part': 'Participle',
277
- 'Ger': 'Gerund', 'Pres': 'Present', 'Past': 'Past', 'Fut': 'Future', 'Perf': 'Perfect', 'Imp': 'Imperfect'
278
- },
279
-
280
- 'fr': {
281
- 'Gender': 'Genre', 'Number': 'Nombre', 'Case': 'Cas', 'Definite': 'Défini', 'PronType': 'Type de Pronom',
282
- 'Person': 'Personne', 'Mood': 'Mode', 'Tense': 'Temps', 'VerbForm': 'Forme Verbale', 'Voice': 'Voix',
283
- 'Fem': 'Féminin', 'Masc': 'Masculin', 'Sing': 'Singulier', 'Plur': 'Pluriel', 'Ind': 'Indicatif',
284
- 'Sub': 'Subjonctif', 'Imp': 'Impératif', 'Inf': 'Infinitif', 'Part': 'Participe',
285
- 'Ger': 'Gérondif', 'Pres': 'Présent', 'Past': 'Passé', 'Fut': 'Futur', 'Perf': 'Parfait', 'Imp': 'Imparfait'
286
- }
287
- }
288
-
289
- def translate_morph(morph_string, lang_code):
290
- for key, value in morph_translations[lang_code].items():
291
- morph_string = morph_string.replace(key, value)
292
- return morph_string
293
-
294
- morphology = morpho_t.get('morphology', 'Morphology')
295
- morph_df[morphology] = morph_df[morphology].apply(lambda x: translate_morph(x, lang_code))
296
-
297
- st.dataframe(morph_df)
298
-
299
- # Mostrar diagramas de arco
300
- with st.expander(morpho_t.get('arc_diagram', 'Syntactic analysis: Arc diagram'), expanded=True):
301
  sentences = list(doc.sents)
302
- arc_diagrams = []
303
-
304
  for i, sent in enumerate(sentences):
305
- st.subheader(f"{morpho_t.get('sentence', 'Sentence')} {i+1}")
306
- html = displacy.render(sent, style="dep", options={"distance": 100})
307
- html = html.replace('height="375"', 'height="200"')
308
- html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html)
309
- html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"',
310
- lambda m: f'<g transform="translate({m.group(1)},50)"', html)
311
- st.write(html, unsafe_allow_html=True)
312
- arc_diagrams.append(html)
313
-
314
- # Botón de exportación
315
- # if st.button(morpho_t.get('export_button', 'Export Analysis')):
316
- # pdf_buffer = export_user_interactions(st.session_state.username, 'morphosyntax')
317
- # st.download_button(
318
- # label=morpho_t.get('download_pdf', 'Download PDF'),
319
- # data=pdf_buffer,
320
- # file_name="morphosyntax_analysis.pdf",
321
- # mime="application/pdf"
322
- # )
 
 
 
1
  #modules/morphosyntax/morphosyntax_interface.py
2
+
3
  import streamlit as st
4
  from streamlit_float import *
5
  from streamlit_antd_components import *
 
11
  import base64
12
  import re
13
 
 
14
  from .morphosyntax_process import (
15
  process_morphosyntactic_input,
16
  format_analysis_results,
17
+ perform_advanced_morphosyntactic_analysis,
18
+ get_repeated_words_colors,
19
  highlight_repeated_words,
20
  POS_COLORS,
21
  POS_TRANSLATIONS
22
  )
23
 
24
  from ..utils.widget_utils import generate_unique_key
 
25
  from ..database.morphosintax_mongo_db import store_student_morphosyntax_result
26
  from ..database.chat_mongo_db import store_chat_history, get_chat_history
27
 
 
 
28
  import logging
29
  logger = logging.getLogger(__name__)
30
 
31
+ ###########################################################################
32
+
33
+ import streamlit as st
34
+ from streamlit_float import *
35
+ from streamlit_antd_components import *
36
+ from streamlit.components.v1 import html
37
+ import spacy
38
+ from spacy import displacy
39
+ import spacy_streamlit
40
+ import pandas as pd
41
+ import base64
42
+ import re
43
+
44
+ ############################################################################
45
+
46
  def display_morphosyntax_interface(lang_code, nlp_models, morpho_t):
47
  try:
48
+ # CSS mejorado para estabilidad y layout vertical
49
+ st.markdown("""
50
+ <style>
51
+ .stTextArea textarea {
52
+ font-size: 1rem;
53
+ line-height: 1.5;
54
+ padding: 0.5rem;
55
+ border-radius: 0.375rem;
56
+ border: 1px solid #e2e8f0;
57
+ background-color: white;
58
+ min-height: 100px !important;
59
+ height: 100px !important;
60
+ }
61
+ .block-container {
62
+ padding-top: 0.5rem !important;
63
+ padding-bottom: 0.5rem !important;
64
+ margin: 0 !important;
65
+ }
66
+ .main-content {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 1rem;
70
+ padding: 0.5rem;
71
+ }
72
+ .arc-diagram-container {
73
+ width: 100%;
74
+ overflow-x: auto;
75
+ background-color: white;
76
+ padding: 0.5rem;
77
+ border-radius: 0.375rem;
78
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
79
+ margin-top: 0.5rem;
80
+ }
81
+ </style>
82
+ """, unsafe_allow_html=True)
83
+
84
+ # Inicialización más robusta del estado
85
  if 'morphosyntax_state' not in st.session_state:
86
  st.session_state.morphosyntax_state = {
87
+ 'original_text': '',
88
+ 'current_text': '',
89
+ 'original_analysis': None,
90
  'analysis_count': 0,
91
+ 'iterations': [] # Inicialización explícita de iterations como lista vacía
92
  }
93
+ else:
94
+ # Asegurar que todas las claves existan
95
+ required_keys = {
96
+ 'original_text': '',
97
+ 'current_text': '',
98
+ 'original_analysis': None,
99
+ 'analysis_count': 0,
100
+ 'iterations': []
101
+ }
102
+ for key, default_value in required_keys.items():
103
+ if key not in st.session_state.morphosyntax_state:
104
+ st.session_state.morphosyntax_state[key] = default_value
105
+
106
+ with st.container():
107
+ # Sección de texto original
108
+ st.markdown("### Texto Original")
109
+
110
+ # Input para texto original
111
+ original_text = st.text_area(
112
+ "Ingrese una oración",
113
+ value=st.session_state.morphosyntax_state['original_text'],
114
+ key="original_text_input",
115
+ placeholder="Ingresar solo una oración hasta el punto y aparte. Si es punto seguido, dejar así.",
116
+ height=100,
117
+ disabled=False
118
+ )
119
 
120
+ # Botón para analizar texto original
121
+ col1, col2, col3 = st.columns([2,1,2])
122
+ with col1:
123
+ analyze_original = st.button(
124
+ "Analizar Texto Original",
125
+ type="primary",
126
+ use_container_width=True,
127
+ disabled=not bool(original_text.strip())
128
+ )
129
 
130
+ # Procesar texto original
131
+ if analyze_original and original_text.strip():
132
+ try:
133
+ with st.spinner("Procesando texto original..."):
134
+ doc = nlp_models[lang_code](original_text)
135
+ analysis = perform_advanced_morphosyntactic_analysis(
136
+ original_text,
137
+ nlp_models[lang_code]
138
+ )
139
+
140
+ # Actualizar estado de forma segura
141
+ st.session_state.morphosyntax_state.update({
142
+ 'original_text': original_text,
143
+ 'current_text': original_text,
144
+ 'original_analysis': {
145
+ 'doc': doc,
146
+ 'advanced_analysis': analysis
147
+ },
148
+ 'iterations': [] # Reiniciar iteraciones al cambiar texto original
149
+ })
150
+
151
+ # Guardar en base de datos
152
+ if store_student_morphosyntax_result(
153
+ username=st.session_state.username,
154
+ text=original_text,
155
+ arc_diagrams=analysis['arc_diagrams']
156
+ ):
157
+ st.success("Texto original analizado exitosamente")
158
+ else:
159
+ st.error("Error al guardar el análisis original")
160
+ except Exception as e:
161
+ logger.error(f"Error procesando texto original: {str(e)}")
162
+ st.error("Error al procesar el texto original")
163
 
164
+ # Mostrar diagrama original
165
+ if st.session_state.morphosyntax_state['original_analysis']:
166
+ display_morphosyntax_results(
167
+ st.session_state.morphosyntax_state['original_analysis'],
168
+ lang_code,
169
+ morpho_t
170
+ )
171
+
172
+ # Sección de iteración
173
+ st.markdown("---")
174
+ st.markdown("### Iteración Actual")
175
+
176
+ # Campo para nueva versión
177
+ iteration_text = st.text_area(
178
+ "Modifique la oración",
179
+ value=st.session_state.morphosyntax_state['current_text'],
180
+ key=f"iteration_input_{st.session_state.morphosyntax_state['analysis_count']}",
181
+ placeholder="Ingresar solo una oración hasta el punto y aparte. Si es punto seguido, dejar así.",
182
+ height=100
183
  )
184
 
185
+ # Botón para analizar iteración
186
+ col1, col2, col3 = st.columns([2,1,2])
187
+ with col1:
188
+ analyze_iteration = st.button(
189
+ "Analizar Cambios",
190
+ type="primary",
191
+ icon="🔍",
192
+ key=f"analyze_{st.session_state.morphosyntax_state['analysis_count']}",
193
+ disabled=not bool(iteration_text.strip()),
194
+ use_container_width=True
195
+ )
196
+
197
+ # Procesar iteración
198
+ if analyze_iteration and iteration_text.strip():
199
+ try:
200
+ with st.spinner("Procesando cambios..."):
201
+ doc = nlp_models[lang_code](iteration_text)
202
+ analysis = perform_advanced_morphosyntactic_analysis(
203
+ iteration_text,
204
+ nlp_models[lang_code]
205
+ )
206
+
207
+ current_analysis = {
208
+ 'doc': doc,
209
+ 'advanced_analysis': analysis
210
+ }
 
 
 
211
 
212
+ # Crear nueva iteración
213
+ new_iteration = {
214
+ 'text': iteration_text,
215
+ 'analysis': current_analysis,
216
+ 'timestamp': pd.Timestamp.now()
217
+ }
218
+
219
+ # Actualizar estado de forma segura
220
+ iterations = st.session_state.morphosyntax_state.get('iterations', [])
221
+ iterations.append(new_iteration)
222
+ st.session_state.morphosyntax_state.update({
223
+ 'current_text': iteration_text,
224
+ 'analysis_count': st.session_state.morphosyntax_state['analysis_count'] + 1,
225
+ 'iterations': iterations
226
+ })
227
+
228
+ if store_student_morphosyntax_result(
229
+ username=st.session_state.username,
230
+ text=iteration_text,
231
+ arc_diagrams=analysis['arc_diagrams']
232
+ ):
233
+ # Mostrar resultados de la iteración
234
+ display_morphosyntax_results(
235
+ current_analysis,
236
+ lang_code,
237
+ morpho_t
238
+ )
239
+ else:
240
+ st.error("Error al guardar la iteración")
241
+ except Exception as e:
242
+ logger.error(f"Error procesando iteración: {str(e)}")
243
+ st.error("Error al procesar los cambios")
244
+
245
+ # Mostrar historial de iteraciones
246
+ if st.session_state.morphosyntax_state.get('iterations', []):
247
+ with st.expander("Historial de Iteraciones", expanded=False):
248
+ for idx, iteration in enumerate(reversed(st.session_state.morphosyntax_state['iterations'])):
249
+ st.markdown(f"**Iteración {idx + 1} ({iteration['timestamp'].strftime('%H:%M:%S')})**")
250
+ st.text_area(
251
+ f"Texto {idx + 1}",
252
+ value=iteration['text'],
253
+ disabled=True,
254
+ height=100,
255
+ key=f"hist_text_{idx}"
256
+ )
257
  display_morphosyntax_results(
258
+ iteration['analysis'],
259
+ lang_code,
260
  morpho_t
261
  )
262
+ st.markdown("---")
263
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  except Exception as e:
265
  logger.error(f"Error general en display_morphosyntax_interface: {str(e)}")
266
  st.error("Se produjo un error. Por favor, intente de nuevo.")
 
267
 
268
+
269
+
270
+
271
+
272
+ #########################################################################3
273
  def display_morphosyntax_results(result, lang_code, morpho_t):
274
  """
275
+ Muestra solo el análisis sintáctico con diagramas de arco.
 
 
 
 
276
  """
 
 
 
277
  if result is None:
278
  st.warning(morpho_t.get('no_results', 'No results available'))
279
  return
280
 
281
  doc = result['doc']
 
 
 
 
 
 
 
 
 
 
282
 
283
+ # Análisis sintáctico (diagramas de arco)
284
+ st.markdown(f"### {morpho_t.get('arc_diagram', 'Syntactic analysis: Arc diagram')}")
 
 
 
285
 
286
+ with st.container():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  sentences = list(doc.sents)
 
 
288
  for i, sent in enumerate(sentences):
289
+ with st.container():
290
+ st.subheader(f"{morpho_t.get('sentence', 'Sentence')} {i+1}")
291
+ try:
292
+ html = displacy.render(sent, style="dep", options={
293
+ "distance": 100,
294
+ "arrow_spacing": 20,
295
+ "word_spacing": 30
296
+ })
297
+ # Ajustar dimensiones del SVG
298
+ html = html.replace('height="375"', 'height="200"')
299
+ html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html)
300
+ html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"',
301
+ lambda m: f'<g transform="translate({m.group(1)},50)"', html)
302
+
303
+ # Envolver en un div con clase para estilos
304
+ html = f'<div class="arc-diagram-container">{html}</div>'
305
+ st.write(html, unsafe_allow_html=True)
306
+ except Exception as e:
307
+ logger.error(f"Error rendering sentence {i}: {str(e)}")
308
+ st.error(f"Error displaying diagram for sentence {i+1}")
modules/morphosyntax/morphosyntax_interface_BackUp_Dec-28-Ok.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #modules/morphosyntax/morphosyntax_interface.py
2
+
3
+ import streamlit as st
4
+ from streamlit_float import *
5
+ from streamlit_antd_components import *
6
+ from streamlit.components.v1 import html
7
+ import spacy
8
+ from spacy import displacy
9
+ import spacy_streamlit
10
+ import pandas as pd
11
+ import base64
12
+ import re
13
+
14
+ from .morphosyntax_process import (
15
+ process_morphosyntactic_input,
16
+ format_analysis_results,
17
+ perform_advanced_morphosyntactic_analysis,
18
+ get_repeated_words_colors,
19
+ highlight_repeated_words,
20
+ POS_COLORS,
21
+ POS_TRANSLATIONS
22
+ )
23
+
24
+ from ..utils.widget_utils import generate_unique_key
25
+ from ..database.morphosintax_mongo_db import store_student_morphosyntax_result
26
+ from ..database.chat_mongo_db import store_chat_history, get_chat_history
27
+
28
+ import logging
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ def display_morphosyntax_interface(lang_code, nlp_models, morpho_t):
33
+ try:
34
+ # Inicializar el estado si no existe
35
+ if 'morphosyntax_state' not in st.session_state:
36
+ st.session_state.morphosyntax_state = {
37
+ 'analysis_count': 0,
38
+ 'current_text': '', # Almacenar el texto actual
39
+ 'last_analysis': None,
40
+ 'needs_update': False # Flag para actualización
41
+ }
42
+
43
+ # Campo de entrada de texto que mantiene su valor
44
+ text_key = "morpho_text_input"
45
+
46
+ # Función para manejar cambios en el texto
47
+ def on_text_change():
48
+ st.session_state.morphosyntax_state['current_text'] = st.session_state[text_key]
49
+ st.session_state.morphosyntax_state['needs_update'] = True
50
+
51
+ # Recuperar el texto anterior si existe
52
+ default_text = st.session_state.morphosyntax_state.get('current_text', '')
53
+
54
+ sentence_input = st.text_area(
55
+ morpho_t.get('morpho_input_label', 'Enter text to analyze'),
56
+ value=default_text, # Usar el texto guardado
57
+ height=150,
58
+ key=text_key,
59
+ on_change=on_text_change,
60
+ placeholder=morpho_t.get('morpho_input_placeholder', 'Enter your text here...')
61
+ )
62
+
63
+ # Botón de análisis
64
+ col1, col2, col3 = st.columns([2,1,2])
65
+ with col1:
66
+ analyze_button = st.button(
67
+ morpho_t.get('morpho_analyze_button', 'Analyze Morphosyntax'),
68
+ key=f"morpho_button_{st.session_state.morphosyntax_state['analysis_count']}",
69
+ type="primary",
70
+ icon="🔍",
71
+ disabled=not bool(sentence_input.strip()),
72
+ use_container_width=True
73
+ )
74
+
75
+ # Procesar análisis solo cuando sea necesario
76
+ if (analyze_button or st.session_state.morphosyntax_state['needs_update']) and sentence_input.strip():
77
+ try:
78
+ with st.spinner(morpho_t.get('processing', 'Processing...')):
79
+ doc = nlp_models[lang_code](sentence_input)
80
+ advanced_analysis = perform_advanced_morphosyntactic_analysis(
81
+ sentence_input,
82
+ nlp_models[lang_code]
83
+ )
84
+
85
+ st.session_state.morphosyntax_result = {
86
+ 'doc': doc,
87
+ 'advanced_analysis': advanced_analysis
88
+ }
89
+
90
+ # Solo guardar en DB si fue un click en el botón
91
+ if analyze_button:
92
+ if store_student_morphosyntax_result(
93
+ username=st.session_state.username,
94
+ text=sentence_input,
95
+ arc_diagrams=advanced_analysis['arc_diagrams']
96
+ ):
97
+ st.success(morpho_t.get('success_message', 'Analysis saved successfully'))
98
+ st.session_state.morphosyntax_state['analysis_count'] += 1
99
+
100
+ st.session_state.morphosyntax_state['needs_update'] = False
101
+
102
+ # Mostrar resultados en un contenedor específico
103
+ with st.container():
104
+ display_morphosyntax_results(
105
+ st.session_state.morphosyntax_result,
106
+ lang_code,
107
+ morpho_t
108
+ )
109
+
110
+ except Exception as e:
111
+ logger.error(f"Error en análisis morfosintáctico: {str(e)}")
112
+ st.error(morpho_t.get('error_processing', f'Error processing text: {str(e)}'))
113
+
114
+ # Mostrar resultados previos si existen
115
+ elif 'morphosyntax_result' in st.session_state and st.session_state.morphosyntax_result:
116
+ with st.container():
117
+ display_morphosyntax_results(
118
+ st.session_state.morphosyntax_result,
119
+ lang_code,
120
+ morpho_t
121
+ )
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error general en display_morphosyntax_interface: {str(e)}")
125
+ st.error("Se produjo un error. Por favor, intente de nuevo.")
126
+
127
+
128
+
129
+ def display_morphosyntax_results(result, lang_code, morpho_t):
130
+ """
131
+ Muestra solo el análisis sintáctico con diagramas de arco.
132
+ """
133
+ if result is None:
134
+ st.warning(morpho_t.get('no_results', 'No results available'))
135
+ return
136
+
137
+ doc = result['doc']
138
+
139
+ # Análisis sintáctico (diagramas de arco)
140
+ st.markdown(f"### {morpho_t.get('arc_diagram', 'Syntactic analysis: Arc diagram')}")
141
+
142
+ with st.container():
143
+ sentences = list(doc.sents)
144
+ for i, sent in enumerate(sentences):
145
+ with st.container():
146
+ st.subheader(f"{morpho_t.get('sentence', 'Sentence')} {i+1}")
147
+ try:
148
+ html = displacy.render(sent, style="dep", options={
149
+ "distance": 100,
150
+ "arrow_spacing": 20,
151
+ "word_spacing": 30
152
+ })
153
+ # Ajustar dimensiones del SVG
154
+ html = html.replace('height="375"', 'height="200"')
155
+ html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html)
156
+ html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"',
157
+ lambda m: f'<g transform="translate({m.group(1)},50)"', html)
158
+
159
+ # Envolver en un div con clase para estilos
160
+ html = f'<div class="arc-diagram-container">{html}</div>'
161
+ st.write(html, unsafe_allow_html=True)
162
+ except Exception as e:
163
+ logger.error(f"Error rendering sentence {i}: {str(e)}")
164
+ st.error(f"Error displaying diagram for sentence {i+1}")
modules/morphosyntax/morphosyntax_process_BackUp_Dec24_Ok.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #modules/morphosyntax/morphosyntax_process.py
2
+ import streamlit as st
3
+
4
+ from ..text_analysis.morpho_analysis import (
5
+ get_repeated_words_colors,
6
+ highlight_repeated_words,
7
+ generate_arc_diagram,
8
+ get_detailed_pos_analysis,
9
+ get_morphological_analysis,
10
+ get_sentence_structure_analysis,
11
+ perform_advanced_morphosyntactic_analysis,
12
+ POS_COLORS,
13
+ POS_TRANSLATIONS
14
+ )
15
+
16
+ from ..database.morphosintax_mongo_db import store_student_morphosyntax_result
17
+
18
+ import logging
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def process_morphosyntactic_input(text, lang_code, nlp_models, t):
23
+ """
24
+ Procesa el texto ingresado para realizar el análisis morfosintáctico.
25
+
26
+ Args:
27
+ text: Texto a analizar
28
+ lang_code: Código del idioma
29
+ nlp_models: Diccionario de modelos spaCy
30
+ t: Diccionario de traducciones
31
+
32
+ Returns:
33
+ tuple: (análisis, visualizaciones, texto_resaltado, mensaje)
34
+ """
35
+ try:
36
+ # Realizar el análisis morfosintáctico
37
+ doc = nlp_models[lang_code](text)
38
+
39
+ # Obtener el análisis avanzado
40
+ analysis = perform_advanced_morphosyntactic_analysis(text, nlp_models[lang_code])
41
+
42
+ # Generar visualizaciones - AQUÍ ESTÁ EL CAMBIO
43
+ arc_diagrams = generate_arc_diagram(doc) # Quitamos lang_code
44
+
45
+ # Obtener palabras repetidas y texto resaltado
46
+ word_colors = get_repeated_words_colors(doc)
47
+ highlighted_text = highlight_repeated_words(doc, word_colors)
48
+
49
+ # Guardar el análisis en la base de datos
50
+ store_student_morphosyntax_result(
51
+ st.session_state.username,
52
+ text,
53
+ {
54
+ 'arc_diagrams': arc_diagrams,
55
+ 'pos_analysis': analysis['pos_analysis'],
56
+ 'morphological_analysis': analysis['morphological_analysis'],
57
+ 'sentence_structure': analysis['sentence_structure']
58
+ }
59
+ )
60
+
61
+ return {
62
+ 'analysis': analysis,
63
+ 'visualizations': arc_diagrams,
64
+ 'highlighted_text': highlighted_text,
65
+ 'success': True,
66
+ 'message': t.get('MORPHOSYNTACTIC', {}).get('success_message', 'Analysis completed successfully')
67
+ }
68
+
69
+ except Exception as e:
70
+ logger.error(f"Error en el análisis morfosintáctico: {str(e)}")
71
+ return {
72
+ 'analysis': None,
73
+ 'visualizations': None,
74
+ 'highlighted_text': None,
75
+ 'success': False,
76
+ 'message': t.get('MORPHOSYNTACTIC', {}).get('error_message', f'Error in analysis: {str(e)}')
77
+ }
78
+
79
+
80
+ def format_analysis_results(analysis_result, t):
81
+ """
82
+ Formatea los resultados del análisis para su visualización.
83
+
84
+ Args:
85
+ analysis_result: Resultado del análisis morfosintáctico
86
+ t: Diccionario de traducciones
87
+
88
+ Returns:
89
+ dict: Resultados formateados para visualización
90
+ """
91
+ morpho_t = t.get('MORPHOSYNTACTIC', {})
92
+
93
+ if not analysis_result['success']:
94
+ return {
95
+ 'formatted_text': analysis_result['message'],
96
+ 'visualizations': None
97
+ }
98
+
99
+ formatted_sections = []
100
+
101
+ # Formato para análisis POS
102
+ if 'pos_analysis' in analysis_result['analysis']:
103
+ pos_section = [f"### {morpho_t.get('pos_analysis', 'Part of Speech Analysis')}"]
104
+ for pos_item in analysis_result['analysis']['pos_analysis']:
105
+ pos_section.append(
106
+ f"- {morpho_t.get(pos_item['pos'], pos_item['pos'])}: "
107
+ f"{pos_item['count']} ({pos_item['percentage']}%)\n "
108
+ f"Ejemplos: {', '.join(pos_item['examples'])}"
109
+ )
110
+ formatted_sections.append('\n'.join(pos_section))
111
+
112
+ # Agregar otras secciones de formato según sea necesario
113
+
114
+ return {
115
+ 'formatted_text': '\n\n'.join(formatted_sections),
116
+ 'visualizations': analysis_result['visualizations'],
117
+ 'highlighted_text': analysis_result['highlighted_text']
118
+ }
119
+
120
+ # Re-exportar las funciones y constantes necesarias
121
+ __all__ = [
122
+ 'process_morphosyntactic_input',
123
+ 'highlight_repeated_words',
124
+ 'generate_arc_diagram',
125
+ 'get_repeated_words_colors',
126
+ 'get_detailed_pos_analysis',
127
+ 'get_morphological_analysis',
128
+ 'get_sentence_structure_analysis',
129
+ 'perform_advanced_morphosyntactic_analysis',
130
+ 'POS_COLORS',
131
+ 'POS_TRANSLATIONS'
132
+ ]
modules/studentact/current_situation_analysis-FAIL.py ADDED
@@ -0,0 +1,810 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #v3/modules/studentact/current_situation_analysis.py
2
+
3
+ import streamlit as st
4
+ import matplotlib.pyplot as plt
5
+ import networkx as nx
6
+ import seaborn as sns
7
+ from collections import Counter
8
+ from itertools import combinations
9
+ import numpy as np
10
+ import matplotlib.patches as patches
11
+ import logging
12
+
13
+ # 2. Configuración básica del logging
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
17
+ handlers=[
18
+ logging.StreamHandler(),
19
+ logging.FileHandler('app.log')
20
+ ]
21
+ )
22
+
23
+ # 3. Obtener el logger específico para este módulo
24
+ logger = logging.getLogger(__name__)
25
+
26
+ #########################################################################
27
+
28
+ def correlate_metrics(scores):
29
+ """
30
+ Ajusta los scores para mantener correlaciones lógicas entre métricas.
31
+
32
+ Args:
33
+ scores: dict con scores iniciales de vocabulario, estructura, cohesión y claridad
34
+
35
+ Returns:
36
+ dict con scores ajustados
37
+ """
38
+ try:
39
+ # 1. Correlación estructura-cohesión
40
+ # La cohesión no puede ser menor que estructura * 0.7
41
+ min_cohesion = scores['structure']['normalized_score'] * 0.7
42
+ if scores['cohesion']['normalized_score'] < min_cohesion:
43
+ scores['cohesion']['normalized_score'] = min_cohesion
44
+
45
+ # 2. Correlación vocabulario-cohesión
46
+ # La cohesión léxica depende del vocabulario
47
+ vocab_influence = scores['vocabulary']['normalized_score'] * 0.6
48
+ scores['cohesion']['normalized_score'] = max(
49
+ scores['cohesion']['normalized_score'],
50
+ vocab_influence
51
+ )
52
+
53
+ # 3. Correlación cohesión-claridad
54
+ # La claridad no puede superar cohesión * 1.2
55
+ max_clarity = scores['cohesion']['normalized_score'] * 1.2
56
+ if scores['clarity']['normalized_score'] > max_clarity:
57
+ scores['clarity']['normalized_score'] = max_clarity
58
+
59
+ # 4. Correlación estructura-claridad
60
+ # La claridad no puede superar estructura * 1.1
61
+ struct_max_clarity = scores['structure']['normalized_score'] * 1.1
62
+ scores['clarity']['normalized_score'] = min(
63
+ scores['clarity']['normalized_score'],
64
+ struct_max_clarity
65
+ )
66
+
67
+ # Normalizar todos los scores entre 0 y 1
68
+ for metric in scores:
69
+ scores[metric]['normalized_score'] = max(0.0, min(1.0, scores[metric]['normalized_score']))
70
+
71
+ return scores
72
+
73
+ except Exception as e:
74
+ logger.error(f"Error en correlate_metrics: {str(e)}")
75
+ return scores
76
+
77
+ ##########################################################################
78
+
79
+ def analyze_text_dimensions(doc):
80
+ """
81
+ Analiza las dimensiones principales del texto manteniendo correlaciones lógicas.
82
+ """
83
+ try:
84
+ # Obtener scores iniciales
85
+ vocab_score, vocab_details = analyze_vocabulary_diversity(doc)
86
+ struct_score = analyze_structure(doc)
87
+ cohesion_score = analyze_cohesion(doc)
88
+ clarity_score, clarity_details = analyze_clarity(doc)
89
+
90
+ # Crear diccionario de scores inicial
91
+ scores = {
92
+ 'vocabulary': {
93
+ 'normalized_score': vocab_score,
94
+ 'details': vocab_details
95
+ },
96
+ 'structure': {
97
+ 'normalized_score': struct_score,
98
+ 'details': None
99
+ },
100
+ 'cohesion': {
101
+ 'normalized_score': cohesion_score,
102
+ 'details': None
103
+ },
104
+ 'clarity': {
105
+ 'normalized_score': clarity_score,
106
+ 'details': clarity_details
107
+ }
108
+ }
109
+
110
+ # Ajustar correlaciones entre métricas
111
+ adjusted_scores = correlate_metrics(scores)
112
+
113
+ # Logging para diagnóstico
114
+ logger.info(f"""
115
+ Scores originales vs ajustados:
116
+ Vocabulario: {vocab_score:.2f} -> {adjusted_scores['vocabulary']['normalized_score']:.2f}
117
+ Estructura: {struct_score:.2f} -> {adjusted_scores['structure']['normalized_score']:.2f}
118
+ Cohesión: {cohesion_score:.2f} -> {adjusted_scores['cohesion']['normalized_score']:.2f}
119
+ Claridad: {clarity_score:.2f} -> {adjusted_scores['clarity']['normalized_score']:.2f}
120
+ """)
121
+
122
+ return adjusted_scores
123
+
124
+ except Exception as e:
125
+ logger.error(f"Error en analyze_text_dimensions: {str(e)}")
126
+ return {
127
+ 'vocabulary': {'normalized_score': 0.0, 'details': {}},
128
+ 'structure': {'normalized_score': 0.0, 'details': {}},
129
+ 'cohesion': {'normalized_score': 0.0, 'details': {}},
130
+ 'clarity': {'normalized_score': 0.0, 'details': {}}
131
+ }
132
+
133
+
134
+
135
+ #############################################################################################
136
+
137
+ def analyze_clarity(doc):
138
+ """
139
+ Analiza la claridad del texto considerando múltiples factores.
140
+ """
141
+ try:
142
+ sentences = list(doc.sents)
143
+ if not sentences:
144
+ return 0.0, {}
145
+
146
+ # 1. Longitud de oraciones
147
+ sentence_lengths = [len(sent) for sent in sentences]
148
+ avg_length = sum(sentence_lengths) / len(sentences)
149
+
150
+ # Normalizar usando los umbrales definidos para clarity
151
+ length_score = normalize_score(
152
+ value=avg_length,
153
+ metric_type='clarity',
154
+ optimal_length=20, # Una oración ideal tiene ~20 palabras
155
+ min_threshold=0.60, # Consistente con METRIC_THRESHOLDS
156
+ target_threshold=0.75 # Consistente con METRIC_THRESHOLDS
157
+ )
158
+
159
+ # 2. Análisis de conectores
160
+ connector_count = 0
161
+ connector_weights = {
162
+ 'CCONJ': 1.0, # Coordinantes
163
+ 'SCONJ': 1.2, # Subordinantes
164
+ 'ADV': 0.8 # Adverbios conectivos
165
+ }
166
+
167
+ for token in doc:
168
+ if token.pos_ in connector_weights and token.dep_ in ['cc', 'mark', 'advmod']:
169
+ connector_count += connector_weights[token.pos_]
170
+
171
+ # Normalizar conectores por oración
172
+ connectors_per_sentence = connector_count / len(sentences) if sentences else 0
173
+ connector_score = normalize_score(
174
+ value=connectors_per_sentence,
175
+ metric_type='clarity',
176
+ optimal_connections=1.5, # ~1.5 conectores por oración es óptimo
177
+ min_threshold=0.60,
178
+ target_threshold=0.75
179
+ )
180
+
181
+ # 3. Complejidad estructural
182
+ clause_count = 0
183
+ for sent in sentences:
184
+ verbs = [token for token in sent if token.pos_ == 'VERB']
185
+ clause_count += len(verbs)
186
+
187
+ complexity_raw = clause_count / len(sentences) if sentences else 0
188
+ complexity_score = normalize_score(
189
+ value=complexity_raw,
190
+ metric_type='clarity',
191
+ optimal_depth=2.0, # ~2 cláusulas por oración es óptimo
192
+ min_threshold=0.60,
193
+ target_threshold=0.75
194
+ )
195
+
196
+ # 4. Densidad léxica
197
+ content_words = len([token for token in doc if token.pos_ in ['NOUN', 'VERB', 'ADJ', 'ADV']])
198
+ total_words = len([token for token in doc if token.is_alpha])
199
+ density = content_words / total_words if total_words > 0 else 0
200
+
201
+ density_score = normalize_score(
202
+ value=density,
203
+ metric_type='clarity',
204
+ optimal_connections=0.6, # 60% de palabras de contenido es óptimo
205
+ min_threshold=0.60,
206
+ target_threshold=0.75
207
+ )
208
+
209
+ # Score final ponderado
210
+ weights = {
211
+ 'length': 0.3,
212
+ 'connectors': 0.3,
213
+ 'complexity': 0.2,
214
+ 'density': 0.2
215
+ }
216
+
217
+ clarity_score = (
218
+ weights['length'] * length_score +
219
+ weights['connectors'] * connector_score +
220
+ weights['complexity'] * complexity_score +
221
+ weights['density'] * density_score
222
+ )
223
+
224
+ details = {
225
+ 'length_score': length_score,
226
+ 'connector_score': connector_score,
227
+ 'complexity_score': complexity_score,
228
+ 'density_score': density_score,
229
+ 'avg_sentence_length': avg_length,
230
+ 'connectors_per_sentence': connectors_per_sentence,
231
+ 'density': density
232
+ }
233
+
234
+ # Agregar logging para diagnóstico
235
+ logger.info(f"""
236
+ Scores de Claridad:
237
+ - Longitud: {length_score:.2f} (avg={avg_length:.1f} palabras)
238
+ - Conectores: {connector_score:.2f} (avg={connectors_per_sentence:.1f} por oración)
239
+ - Complejidad: {complexity_score:.2f} (avg={complexity_raw:.1f} cláusulas)
240
+ - Densidad: {density_score:.2f} ({density*100:.1f}% palabras de contenido)
241
+ - Score Final: {clarity_score:.2f}
242
+ """)
243
+
244
+ return clarity_score, details
245
+
246
+ except Exception as e:
247
+ logger.error(f"Error en analyze_clarity: {str(e)}")
248
+ return 0.0, {}
249
+
250
+
251
+ def analyze_vocabulary_diversity(doc):
252
+ """Análisis mejorado de la diversidad y calidad del vocabulario"""
253
+ try:
254
+ # 1. Análisis básico de diversidad
255
+ unique_lemmas = {token.lemma_ for token in doc if token.is_alpha}
256
+ total_words = len([token for token in doc if token.is_alpha])
257
+ basic_diversity = len(unique_lemmas) / total_words if total_words > 0 else 0
258
+
259
+ # 2. Análisis de registro
260
+ academic_words = 0
261
+ narrative_words = 0
262
+ technical_terms = 0
263
+
264
+ # Clasificar palabras por registro
265
+ for token in doc:
266
+ if token.is_alpha:
267
+ # Detectar términos académicos/técnicos
268
+ if token.pos_ in ['NOUN', 'VERB', 'ADJ']:
269
+ if any(parent.pos_ == 'NOUN' for parent in token.ancestors):
270
+ technical_terms += 1
271
+ # Detectar palabras narrativas
272
+ if token.pos_ in ['VERB', 'ADV'] and token.dep_ in ['ROOT', 'advcl']:
273
+ narrative_words += 1
274
+
275
+ # 3. Análisis de complejidad sintáctica
276
+ avg_sentence_length = sum(len(sent) for sent in doc.sents) / len(list(doc.sents))
277
+
278
+ # 4. Calcular score ponderado
279
+ weights = {
280
+ 'diversity': 0.3,
281
+ 'technical': 0.3,
282
+ 'narrative': 0.2,
283
+ 'complexity': 0.2
284
+ }
285
+
286
+ scores = {
287
+ 'diversity': basic_diversity,
288
+ 'technical': technical_terms / total_words if total_words > 0 else 0,
289
+ 'narrative': narrative_words / total_words if total_words > 0 else 0,
290
+ 'complexity': min(1.0, avg_sentence_length / 20) # Normalizado a 20 palabras
291
+ }
292
+
293
+ # Score final ponderado
294
+ final_score = sum(weights[key] * scores[key] for key in weights)
295
+
296
+ # Información adicional para diagnóstico
297
+ details = {
298
+ 'text_type': 'narrative' if scores['narrative'] > scores['technical'] else 'academic',
299
+ 'scores': scores
300
+ }
301
+
302
+ return final_score, details
303
+
304
+ except Exception as e:
305
+ logger.error(f"Error en analyze_vocabulary_diversity: {str(e)}")
306
+ return 0.0, {}
307
+
308
+ def analyze_cohesion(doc):
309
+ """Analiza la cohesión textual"""
310
+ try:
311
+ sentences = list(doc.sents)
312
+ if len(sentences) < 2:
313
+ logger.warning("Texto demasiado corto para análisis de cohesión")
314
+ return 0.0
315
+
316
+ # 1. Análisis de conexiones léxicas
317
+ lexical_connections = 0
318
+ total_possible_connections = 0
319
+
320
+ for i in range(len(sentences)-1):
321
+ # Obtener lemmas significativos (no stopwords)
322
+ sent1_words = {token.lemma_ for token in sentences[i]
323
+ if token.is_alpha and not token.is_stop}
324
+ sent2_words = {token.lemma_ for token in sentences[i+1]
325
+ if token.is_alpha and not token.is_stop}
326
+
327
+ if sent1_words and sent2_words: # Verificar que ambos conjuntos no estén vacíos
328
+ intersection = len(sent1_words.intersection(sent2_words))
329
+ total_possible = min(len(sent1_words), len(sent2_words))
330
+
331
+ if total_possible > 0:
332
+ lexical_score = intersection / total_possible
333
+ lexical_connections += lexical_score
334
+ total_possible_connections += 1
335
+
336
+ # 2. Análisis de conectores
337
+ connector_count = 0
338
+ connector_types = {
339
+ 'CCONJ': 1.0, # Coordinantes
340
+ 'SCONJ': 1.2, # Subordinantes
341
+ 'ADV': 0.8 # Adverbios conectivos
342
+ }
343
+
344
+ for token in doc:
345
+ if (token.pos_ in connector_types and
346
+ token.dep_ in ['cc', 'mark', 'advmod'] and
347
+ not token.is_stop):
348
+ connector_count += connector_types[token.pos_]
349
+
350
+ # 3. Cálculo de scores normalizados
351
+ if total_possible_connections > 0:
352
+ lexical_cohesion = lexical_connections / total_possible_connections
353
+ else:
354
+ lexical_cohesion = 0
355
+
356
+ if len(sentences) > 1:
357
+ connector_cohesion = min(1.0, connector_count / (len(sentences) - 1))
358
+ else:
359
+ connector_cohesion = 0
360
+
361
+ # 4. Score final ponderado
362
+ weights = {
363
+ 'lexical': 0.7,
364
+ 'connectors': 0.3
365
+ }
366
+
367
+ cohesion_score = (
368
+ weights['lexical'] * lexical_cohesion +
369
+ weights['connectors'] * connector_cohesion
370
+ )
371
+
372
+ # 5. Logging para diagnóstico
373
+ logger.info(f"""
374
+ Análisis de Cohesión:
375
+ - Conexiones léxicas encontradas: {lexical_connections}
376
+ - Conexiones posibles: {total_possible_connections}
377
+ - Lexical cohesion score: {lexical_cohesion}
378
+ - Conectores encontrados: {connector_count}
379
+ - Connector cohesion score: {connector_cohesion}
380
+ - Score final: {cohesion_score}
381
+ """)
382
+
383
+ return cohesion_score
384
+
385
+ except Exception as e:
386
+ logger.error(f"Error en analyze_cohesion: {str(e)}")
387
+ return 0.0
388
+
389
+ def analyze_structure(doc):
390
+ try:
391
+ if len(doc) == 0:
392
+ return 0.0
393
+
394
+ structure_scores = []
395
+ for token in doc:
396
+ if token.dep_ == 'ROOT':
397
+ result = get_dependency_depths(token)
398
+ structure_scores.append(result['final_score'])
399
+
400
+ if not structure_scores:
401
+ return 0.0
402
+
403
+ return min(1.0, sum(structure_scores) / len(structure_scores))
404
+
405
+ except Exception as e:
406
+ logger.error(f"Error en analyze_structure: {str(e)}")
407
+ return 0.0
408
+
409
+ # Funciones auxiliares de análisis
410
+
411
+ def get_dependency_depths(token, depth=0, analyzed_tokens=None):
412
+ """
413
+ Analiza la profundidad y calidad de las relaciones de dependencia.
414
+
415
+ Args:
416
+ token: Token a analizar
417
+ depth: Profundidad actual en el árbol
418
+ analyzed_tokens: Set para evitar ciclos en el análisis
419
+
420
+ Returns:
421
+ dict: Información detallada sobre las dependencias
422
+ - depths: Lista de profundidades
423
+ - relations: Diccionario con tipos de relaciones encontradas
424
+ - complexity_score: Puntuación de complejidad
425
+ """
426
+ if analyzed_tokens is None:
427
+ analyzed_tokens = set()
428
+
429
+ # Evitar ciclos
430
+ if token.i in analyzed_tokens:
431
+ return {
432
+ 'depths': [],
433
+ 'relations': {},
434
+ 'complexity_score': 0
435
+ }
436
+
437
+ analyzed_tokens.add(token.i)
438
+
439
+ # Pesos para diferentes tipos de dependencias
440
+ dependency_weights = {
441
+ # Dependencias principales
442
+ 'nsubj': 1.2, # Sujeto nominal
443
+ 'obj': 1.1, # Objeto directo
444
+ 'iobj': 1.1, # Objeto indirecto
445
+ 'ROOT': 1.3, # Raíz
446
+
447
+ # Modificadores
448
+ 'amod': 0.8, # Modificador adjetival
449
+ 'advmod': 0.8, # Modificador adverbial
450
+ 'nmod': 0.9, # Modificador nominal
451
+
452
+ # Estructuras complejas
453
+ 'csubj': 1.4, # Cláusula como sujeto
454
+ 'ccomp': 1.3, # Complemento clausal
455
+ 'xcomp': 1.2, # Complemento clausal abierto
456
+ 'advcl': 1.2, # Cláusula adverbial
457
+
458
+ # Coordinación y subordinación
459
+ 'conj': 1.1, # Conjunción
460
+ 'cc': 0.7, # Coordinación
461
+ 'mark': 0.8, # Marcador
462
+
463
+ # Otros
464
+ 'det': 0.5, # Determinante
465
+ 'case': 0.5, # Caso
466
+ 'punct': 0.1 # Puntuación
467
+ }
468
+
469
+ # Inicializar resultados
470
+ current_result = {
471
+ 'depths': [depth],
472
+ 'relations': {token.dep_: 1},
473
+ 'complexity_score': dependency_weights.get(token.dep_, 0.5) * (depth + 1)
474
+ }
475
+
476
+ # Analizar hijos recursivamente
477
+ for child in token.children:
478
+ child_result = get_dependency_depths(child, depth + 1, analyzed_tokens)
479
+
480
+ # Combinar profundidades
481
+ current_result['depths'].extend(child_result['depths'])
482
+
483
+ # Combinar relaciones
484
+ for rel, count in child_result['relations'].items():
485
+ current_result['relations'][rel] = current_result['relations'].get(rel, 0) + count
486
+
487
+ # Acumular score de complejidad
488
+ current_result['complexity_score'] += child_result['complexity_score']
489
+
490
+ # Calcular métricas adicionales
491
+ current_result['max_depth'] = max(current_result['depths'])
492
+ current_result['avg_depth'] = sum(current_result['depths']) / len(current_result['depths'])
493
+ current_result['relation_diversity'] = len(current_result['relations'])
494
+
495
+ # Calcular score ponderado por tipo de estructura
496
+ structure_bonus = 0
497
+
498
+ # Bonus por estructuras complejas
499
+ if 'csubj' in current_result['relations'] or 'ccomp' in current_result['relations']:
500
+ structure_bonus += 0.3
501
+
502
+ # Bonus por coordinación balanceada
503
+ if 'conj' in current_result['relations'] and 'cc' in current_result['relations']:
504
+ structure_bonus += 0.2
505
+
506
+ # Bonus por modificación rica
507
+ if len(set(['amod', 'advmod', 'nmod']) & set(current_result['relations'])) >= 2:
508
+ structure_bonus += 0.2
509
+
510
+ current_result['final_score'] = (
511
+ current_result['complexity_score'] * (1 + structure_bonus)
512
+ )
513
+
514
+ return current_result
515
+
516
+ def normalize_score(value, metric_type,
517
+ min_threshold=0.0, target_threshold=1.0,
518
+ range_factor=2.0, optimal_length=None,
519
+ optimal_connections=None, optimal_depth=None):
520
+ """
521
+ Normaliza un valor considerando umbrales específicos por tipo de métrica.
522
+
523
+ Args:
524
+ value: Valor a normalizar
525
+ metric_type: Tipo de métrica ('vocabulary', 'structure', 'cohesion', 'clarity')
526
+ min_threshold: Valor mínimo aceptable
527
+ target_threshold: Valor objetivo
528
+ range_factor: Factor para ajustar el rango
529
+ optimal_length: Longitud óptima (opcional)
530
+ optimal_connections: Número óptimo de conexiones (opcional)
531
+ optimal_depth: Profundidad óptima de estructura (opcional)
532
+
533
+ Returns:
534
+ float: Valor normalizado entre 0 y 1
535
+ """
536
+ try:
537
+ # Definir umbrales por tipo de métrica
538
+ METRIC_THRESHOLDS = {
539
+ 'vocabulary': {
540
+ 'min': 0.60,
541
+ 'target': 0.75,
542
+ 'range_factor': 1.5
543
+ },
544
+ 'structure': {
545
+ 'min': 0.65,
546
+ 'target': 0.80,
547
+ 'range_factor': 1.8
548
+ },
549
+ 'cohesion': {
550
+ 'min': 0.55,
551
+ 'target': 0.70,
552
+ 'range_factor': 1.6
553
+ },
554
+ 'clarity': {
555
+ 'min': 0.60,
556
+ 'target': 0.75,
557
+ 'range_factor': 1.7
558
+ }
559
+ }
560
+
561
+ # Validar valores negativos o cero
562
+ if value < 0:
563
+ logger.warning(f"Valor negativo recibido: {value}")
564
+ return 0.0
565
+
566
+ # Manejar caso donde el valor es cero
567
+ if value == 0:
568
+ logger.warning("Valor cero recibido")
569
+ return 0.0
570
+
571
+ # Obtener umbrales específicos para el tipo de métrica
572
+ thresholds = METRIC_THRESHOLDS.get(metric_type, {
573
+ 'min': min_threshold,
574
+ 'target': target_threshold,
575
+ 'range_factor': range_factor
576
+ })
577
+
578
+ # Identificar el valor de referencia a usar
579
+ if optimal_depth is not None:
580
+ reference = optimal_depth
581
+ elif optimal_connections is not None:
582
+ reference = optimal_connections
583
+ elif optimal_length is not None:
584
+ reference = optimal_length
585
+ else:
586
+ reference = thresholds['target']
587
+
588
+ # Validar valor de referencia
589
+ if reference <= 0:
590
+ logger.warning(f"Valor de referencia inválido: {reference}")
591
+ return 0.0
592
+
593
+ # Calcular score basado en umbrales
594
+ if value < thresholds['min']:
595
+ # Valor por debajo del mínimo
596
+ score = (value / thresholds['min']) * 0.5 # Máximo 0.5 para valores bajo el mínimo
597
+ elif value < thresholds['target']:
598
+ # Valor entre mínimo y objetivo
599
+ range_size = thresholds['target'] - thresholds['min']
600
+ progress = (value - thresholds['min']) / range_size
601
+ score = 0.5 + (progress * 0.5) # Escala entre 0.5 y 1.0
602
+ else:
603
+ # Valor alcanza o supera el objetivo
604
+ score = 1.0
605
+
606
+ # Penalizar valores muy por encima del objetivo
607
+ if value > (thresholds['target'] * thresholds['range_factor']):
608
+ excess = (value - thresholds['target']) / (thresholds['target'] * thresholds['range_factor'])
609
+ score = max(0.7, 1.0 - excess) # No bajar de 0.7 para valores altos
610
+
611
+ # Asegurar que el resultado esté entre 0 y 1
612
+ return max(0.0, min(1.0, score))
613
+
614
+ except Exception as e:
615
+ logger.error(f"Error en normalize_score: {str(e)}")
616
+ return 0.0
617
+
618
+
619
+ # Funciones de generación de gráficos
620
+ def generate_sentence_graphs(doc):
621
+ """Genera visualizaciones de estructura de oraciones"""
622
+ fig, ax = plt.subplots(figsize=(10, 6))
623
+ # Implementar visualización
624
+ plt.close()
625
+ return fig
626
+
627
+ def generate_word_connections(doc):
628
+ """Genera red de conexiones de palabras"""
629
+ fig, ax = plt.subplots(figsize=(10, 6))
630
+ # Implementar visualización
631
+ plt.close()
632
+ return fig
633
+
634
+ def generate_connection_paths(doc):
635
+ """Genera patrones de conexión"""
636
+ fig, ax = plt.subplots(figsize=(10, 6))
637
+ # Implementar visualización
638
+ plt.close()
639
+ return fig
640
+
641
+ def create_vocabulary_network(doc):
642
+ """
643
+ Genera el grafo de red de vocabulario.
644
+ """
645
+ G = nx.Graph()
646
+
647
+ # Crear nodos para palabras significativas
648
+ words = [token.text.lower() for token in doc if token.is_alpha and not token.is_stop]
649
+ word_freq = Counter(words)
650
+
651
+ # Añadir nodos con tamaño basado en frecuencia
652
+ for word, freq in word_freq.items():
653
+ G.add_node(word, size=freq)
654
+
655
+ # Crear conexiones basadas en co-ocurrencia
656
+ window_size = 5
657
+ for i in range(len(words) - window_size):
658
+ window = words[i:i+window_size]
659
+ for w1, w2 in combinations(set(window), 2):
660
+ if G.has_edge(w1, w2):
661
+ G[w1][w2]['weight'] += 1
662
+ else:
663
+ G.add_edge(w1, w2, weight=1)
664
+
665
+ # Crear visualización
666
+ fig, ax = plt.subplots(figsize=(12, 8))
667
+ pos = nx.spring_layout(G)
668
+
669
+ # Dibujar nodos
670
+ nx.draw_networkx_nodes(G, pos,
671
+ node_size=[G.nodes[node]['size']*100 for node in G.nodes],
672
+ node_color='lightblue',
673
+ alpha=0.7)
674
+
675
+ # Dibujar conexiones
676
+ nx.draw_networkx_edges(G, pos,
677
+ width=[G[u][v]['weight']*0.5 for u,v in G.edges],
678
+ alpha=0.5)
679
+
680
+ # Añadir etiquetas
681
+ nx.draw_networkx_labels(G, pos)
682
+
683
+ plt.title("Red de Vocabulario")
684
+ plt.axis('off')
685
+ return fig
686
+
687
+ def create_syntax_complexity_graph(doc):
688
+ """
689
+ Genera el diagrama de arco de complejidad sintáctica.
690
+ Muestra la estructura de dependencias con colores basados en la complejidad.
691
+ """
692
+ try:
693
+ # Preparar datos para la visualización
694
+ sentences = list(doc.sents)
695
+ if not sentences:
696
+ return None
697
+
698
+ # Crear figura para el gráfico
699
+ fig, ax = plt.subplots(figsize=(12, len(sentences) * 2))
700
+
701
+ # Colores para diferentes niveles de profundidad
702
+ depth_colors = plt.cm.viridis(np.linspace(0, 1, 6))
703
+
704
+ y_offset = 0
705
+ max_x = 0
706
+
707
+ for sent in sentences:
708
+ words = [token.text for token in sent]
709
+ x_positions = range(len(words))
710
+ max_x = max(max_x, len(words))
711
+
712
+ # Dibujar palabras
713
+ plt.plot(x_positions, [y_offset] * len(words), 'k-', alpha=0.2)
714
+ plt.scatter(x_positions, [y_offset] * len(words), alpha=0)
715
+
716
+ # Añadir texto
717
+ for i, word in enumerate(words):
718
+ plt.annotate(word, (i, y_offset), xytext=(0, -10),
719
+ textcoords='offset points', ha='center')
720
+
721
+ # Dibujar arcos de dependencia
722
+ for token in sent:
723
+ if token.dep_ != "ROOT":
724
+ # Calcular profundidad de dependencia
725
+ depth = 0
726
+ current = token
727
+ while current.head != current:
728
+ depth += 1
729
+ current = current.head
730
+
731
+ # Determinar posiciones para el arco
732
+ start = token.i - sent[0].i
733
+ end = token.head.i - sent[0].i
734
+
735
+ # Altura del arco basada en la distancia entre palabras
736
+ height = 0.5 * abs(end - start)
737
+
738
+ # Color basado en la profundidad
739
+ color = depth_colors[min(depth, len(depth_colors)-1)]
740
+
741
+ # Crear arco
742
+ arc = patches.Arc((min(start, end) + abs(end - start)/2, y_offset),
743
+ width=abs(end - start),
744
+ height=height,
745
+ angle=0,
746
+ theta1=0,
747
+ theta2=180,
748
+ color=color,
749
+ alpha=0.6)
750
+ ax.add_patch(arc)
751
+
752
+ y_offset -= 2
753
+
754
+ # Configurar el gráfico
755
+ plt.xlim(-1, max_x)
756
+ plt.ylim(y_offset - 1, 1)
757
+ plt.axis('off')
758
+ plt.title("Complejidad Sintáctica")
759
+
760
+ return fig
761
+
762
+ except Exception as e:
763
+ logger.error(f"Error en create_syntax_complexity_graph: {str(e)}")
764
+ return None
765
+
766
+
767
+ def create_cohesion_heatmap(doc):
768
+ """Genera un mapa de calor que muestra la cohesión entre párrafos/oraciones."""
769
+ try:
770
+ sentences = list(doc.sents)
771
+ n_sentences = len(sentences)
772
+
773
+ if n_sentences < 2:
774
+ return None
775
+
776
+ similarity_matrix = np.zeros((n_sentences, n_sentences))
777
+
778
+ for i in range(n_sentences):
779
+ for j in range(n_sentences):
780
+ sent1_lemmas = {token.lemma_ for token in sentences[i]
781
+ if token.is_alpha and not token.is_stop}
782
+ sent2_lemmas = {token.lemma_ for token in sentences[j]
783
+ if token.is_alpha and not token.is_stop}
784
+
785
+ if sent1_lemmas and sent2_lemmas:
786
+ intersection = len(sent1_lemmas & sent2_lemmas) # Corregido aquí
787
+ union = len(sent1_lemmas | sent2_lemmas) # Y aquí
788
+ similarity_matrix[i, j] = intersection / union if union > 0 else 0
789
+
790
+ # Crear visualización
791
+ fig, ax = plt.subplots(figsize=(10, 8))
792
+
793
+ sns.heatmap(similarity_matrix,
794
+ cmap='YlOrRd',
795
+ square=True,
796
+ xticklabels=False,
797
+ yticklabels=False,
798
+ cbar_kws={'label': 'Cohesión'},
799
+ ax=ax)
800
+
801
+ plt.title("Mapa de Cohesión Textual")
802
+ plt.xlabel("Oraciones")
803
+ plt.ylabel("Oraciones")
804
+
805
+ plt.tight_layout()
806
+ return fig
807
+
808
+ except Exception as e:
809
+ logger.error(f"Error en create_cohesion_heatmap: {str(e)}")
810
+ return None
modules/studentact/current_situation_analysis.py ADDED
@@ -0,0 +1,810 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #v3/modules/studentact/current_situation_analysis.py
2
+
3
+ import streamlit as st
4
+ import matplotlib.pyplot as plt
5
+ import networkx as nx
6
+ import seaborn as sns
7
+ from collections import Counter
8
+ from itertools import combinations
9
+ import numpy as np
10
+ import matplotlib.patches as patches
11
+ import logging
12
+
13
+ # 2. Configuración básica del logging
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
17
+ handlers=[
18
+ logging.StreamHandler(),
19
+ logging.FileHandler('app.log')
20
+ ]
21
+ )
22
+
23
+ # 3. Obtener el logger específico para este módulo
24
+ logger = logging.getLogger(__name__)
25
+
26
+ #########################################################################
27
+
28
+ def correlate_metrics(scores):
29
+ """
30
+ Ajusta los scores para mantener correlaciones lógicas entre métricas.
31
+
32
+ Args:
33
+ scores: dict con scores iniciales de vocabulario, estructura, cohesión y claridad
34
+
35
+ Returns:
36
+ dict con scores ajustados
37
+ """
38
+ try:
39
+ # 1. Correlación estructura-cohesión
40
+ # La cohesión no puede ser menor que estructura * 0.7
41
+ min_cohesion = scores['structure']['normalized_score'] * 0.7
42
+ if scores['cohesion']['normalized_score'] < min_cohesion:
43
+ scores['cohesion']['normalized_score'] = min_cohesion
44
+
45
+ # 2. Correlación vocabulario-cohesión
46
+ # La cohesión léxica depende del vocabulario
47
+ vocab_influence = scores['vocabulary']['normalized_score'] * 0.6
48
+ scores['cohesion']['normalized_score'] = max(
49
+ scores['cohesion']['normalized_score'],
50
+ vocab_influence
51
+ )
52
+
53
+ # 3. Correlación cohesión-claridad
54
+ # La claridad no puede superar cohesión * 1.2
55
+ max_clarity = scores['cohesion']['normalized_score'] * 1.2
56
+ if scores['clarity']['normalized_score'] > max_clarity:
57
+ scores['clarity']['normalized_score'] = max_clarity
58
+
59
+ # 4. Correlación estructura-claridad
60
+ # La claridad no puede superar estructura * 1.1
61
+ struct_max_clarity = scores['structure']['normalized_score'] * 1.1
62
+ scores['clarity']['normalized_score'] = min(
63
+ scores['clarity']['normalized_score'],
64
+ struct_max_clarity
65
+ )
66
+
67
+ # Normalizar todos los scores entre 0 y 1
68
+ for metric in scores:
69
+ scores[metric]['normalized_score'] = max(0.0, min(1.0, scores[metric]['normalized_score']))
70
+
71
+ return scores
72
+
73
+ except Exception as e:
74
+ logger.error(f"Error en correlate_metrics: {str(e)}")
75
+ return scores
76
+
77
+ ##########################################################################
78
+
79
+ def analyze_text_dimensions(doc):
80
+ """
81
+ Analiza las dimensiones principales del texto manteniendo correlaciones lógicas.
82
+ """
83
+ try:
84
+ # Obtener scores iniciales
85
+ vocab_score, vocab_details = analyze_vocabulary_diversity(doc)
86
+ struct_score = analyze_structure(doc)
87
+ cohesion_score = analyze_cohesion(doc)
88
+ clarity_score, clarity_details = analyze_clarity(doc)
89
+
90
+ # Crear diccionario de scores inicial
91
+ scores = {
92
+ 'vocabulary': {
93
+ 'normalized_score': vocab_score,
94
+ 'details': vocab_details
95
+ },
96
+ 'structure': {
97
+ 'normalized_score': struct_score,
98
+ 'details': None
99
+ },
100
+ 'cohesion': {
101
+ 'normalized_score': cohesion_score,
102
+ 'details': None
103
+ },
104
+ 'clarity': {
105
+ 'normalized_score': clarity_score,
106
+ 'details': clarity_details
107
+ }
108
+ }
109
+
110
+ # Ajustar correlaciones entre métricas
111
+ adjusted_scores = correlate_metrics(scores)
112
+
113
+ # Logging para diagnóstico
114
+ logger.info(f"""
115
+ Scores originales vs ajustados:
116
+ Vocabulario: {vocab_score:.2f} -> {adjusted_scores['vocabulary']['normalized_score']:.2f}
117
+ Estructura: {struct_score:.2f} -> {adjusted_scores['structure']['normalized_score']:.2f}
118
+ Cohesión: {cohesion_score:.2f} -> {adjusted_scores['cohesion']['normalized_score']:.2f}
119
+ Claridad: {clarity_score:.2f} -> {adjusted_scores['clarity']['normalized_score']:.2f}
120
+ """)
121
+
122
+ return adjusted_scores
123
+
124
+ except Exception as e:
125
+ logger.error(f"Error en analyze_text_dimensions: {str(e)}")
126
+ return {
127
+ 'vocabulary': {'normalized_score': 0.0, 'details': {}},
128
+ 'structure': {'normalized_score': 0.0, 'details': {}},
129
+ 'cohesion': {'normalized_score': 0.0, 'details': {}},
130
+ 'clarity': {'normalized_score': 0.0, 'details': {}}
131
+ }
132
+
133
+
134
+
135
+ #############################################################################################
136
+
137
+ def analyze_clarity(doc):
138
+ """
139
+ Analiza la claridad del texto considerando múltiples factores.
140
+ """
141
+ try:
142
+ sentences = list(doc.sents)
143
+ if not sentences:
144
+ return 0.0, {}
145
+
146
+ # 1. Longitud de oraciones
147
+ sentence_lengths = [len(sent) for sent in sentences]
148
+ avg_length = sum(sentence_lengths) / len(sentences)
149
+
150
+ # Normalizar usando los umbrales definidos para clarity
151
+ length_score = normalize_score(
152
+ value=avg_length,
153
+ metric_type='clarity',
154
+ optimal_length=20, # Una oración ideal tiene ~20 palabras
155
+ min_threshold=0.60, # Consistente con METRIC_THRESHOLDS
156
+ target_threshold=0.75 # Consistente con METRIC_THRESHOLDS
157
+ )
158
+
159
+ # 2. Análisis de conectores
160
+ connector_count = 0
161
+ connector_weights = {
162
+ 'CCONJ': 1.0, # Coordinantes
163
+ 'SCONJ': 1.2, # Subordinantes
164
+ 'ADV': 0.8 # Adverbios conectivos
165
+ }
166
+
167
+ for token in doc:
168
+ if token.pos_ in connector_weights and token.dep_ in ['cc', 'mark', 'advmod']:
169
+ connector_count += connector_weights[token.pos_]
170
+
171
+ # Normalizar conectores por oración
172
+ connectors_per_sentence = connector_count / len(sentences) if sentences else 0
173
+ connector_score = normalize_score(
174
+ value=connectors_per_sentence,
175
+ metric_type='clarity',
176
+ optimal_connections=1.5, # ~1.5 conectores por oración es óptimo
177
+ min_threshold=0.60,
178
+ target_threshold=0.75
179
+ )
180
+
181
+ # 3. Complejidad estructural
182
+ clause_count = 0
183
+ for sent in sentences:
184
+ verbs = [token for token in sent if token.pos_ == 'VERB']
185
+ clause_count += len(verbs)
186
+
187
+ complexity_raw = clause_count / len(sentences) if sentences else 0
188
+ complexity_score = normalize_score(
189
+ value=complexity_raw,
190
+ metric_type='clarity',
191
+ optimal_depth=2.0, # ~2 cláusulas por oración es óptimo
192
+ min_threshold=0.60,
193
+ target_threshold=0.75
194
+ )
195
+
196
+ # 4. Densidad léxica
197
+ content_words = len([token for token in doc if token.pos_ in ['NOUN', 'VERB', 'ADJ', 'ADV']])
198
+ total_words = len([token for token in doc if token.is_alpha])
199
+ density = content_words / total_words if total_words > 0 else 0
200
+
201
+ density_score = normalize_score(
202
+ value=density,
203
+ metric_type='clarity',
204
+ optimal_connections=0.6, # 60% de palabras de contenido es óptimo
205
+ min_threshold=0.60,
206
+ target_threshold=0.75
207
+ )
208
+
209
+ # Score final ponderado
210
+ weights = {
211
+ 'length': 0.3,
212
+ 'connectors': 0.3,
213
+ 'complexity': 0.2,
214
+ 'density': 0.2
215
+ }
216
+
217
+ clarity_score = (
218
+ weights['length'] * length_score +
219
+ weights['connectors'] * connector_score +
220
+ weights['complexity'] * complexity_score +
221
+ weights['density'] * density_score
222
+ )
223
+
224
+ details = {
225
+ 'length_score': length_score,
226
+ 'connector_score': connector_score,
227
+ 'complexity_score': complexity_score,
228
+ 'density_score': density_score,
229
+ 'avg_sentence_length': avg_length,
230
+ 'connectors_per_sentence': connectors_per_sentence,
231
+ 'density': density
232
+ }
233
+
234
+ # Agregar logging para diagnóstico
235
+ logger.info(f"""
236
+ Scores de Claridad:
237
+ - Longitud: {length_score:.2f} (avg={avg_length:.1f} palabras)
238
+ - Conectores: {connector_score:.2f} (avg={connectors_per_sentence:.1f} por oración)
239
+ - Complejidad: {complexity_score:.2f} (avg={complexity_raw:.1f} cláusulas)
240
+ - Densidad: {density_score:.2f} ({density*100:.1f}% palabras de contenido)
241
+ - Score Final: {clarity_score:.2f}
242
+ """)
243
+
244
+ return clarity_score, details
245
+
246
+ except Exception as e:
247
+ logger.error(f"Error en analyze_clarity: {str(e)}")
248
+ return 0.0, {}
249
+
250
+
251
+ def analyze_vocabulary_diversity(doc):
252
+ """Análisis mejorado de la diversidad y calidad del vocabulario"""
253
+ try:
254
+ # 1. Análisis básico de diversidad
255
+ unique_lemmas = {token.lemma_ for token in doc if token.is_alpha}
256
+ total_words = len([token for token in doc if token.is_alpha])
257
+ basic_diversity = len(unique_lemmas) / total_words if total_words > 0 else 0
258
+
259
+ # 2. Análisis de registro
260
+ academic_words = 0
261
+ narrative_words = 0
262
+ technical_terms = 0
263
+
264
+ # Clasificar palabras por registro
265
+ for token in doc:
266
+ if token.is_alpha:
267
+ # Detectar términos académicos/técnicos
268
+ if token.pos_ in ['NOUN', 'VERB', 'ADJ']:
269
+ if any(parent.pos_ == 'NOUN' for parent in token.ancestors):
270
+ technical_terms += 1
271
+ # Detectar palabras narrativas
272
+ if token.pos_ in ['VERB', 'ADV'] and token.dep_ in ['ROOT', 'advcl']:
273
+ narrative_words += 1
274
+
275
+ # 3. Análisis de complejidad sintáctica
276
+ avg_sentence_length = sum(len(sent) for sent in doc.sents) / len(list(doc.sents))
277
+
278
+ # 4. Calcular score ponderado
279
+ weights = {
280
+ 'diversity': 0.3,
281
+ 'technical': 0.3,
282
+ 'narrative': 0.2,
283
+ 'complexity': 0.2
284
+ }
285
+
286
+ scores = {
287
+ 'diversity': basic_diversity,
288
+ 'technical': technical_terms / total_words if total_words > 0 else 0,
289
+ 'narrative': narrative_words / total_words if total_words > 0 else 0,
290
+ 'complexity': min(1.0, avg_sentence_length / 20) # Normalizado a 20 palabras
291
+ }
292
+
293
+ # Score final ponderado
294
+ final_score = sum(weights[key] * scores[key] for key in weights)
295
+
296
+ # Información adicional para diagnóstico
297
+ details = {
298
+ 'text_type': 'narrative' if scores['narrative'] > scores['technical'] else 'academic',
299
+ 'scores': scores
300
+ }
301
+
302
+ return final_score, details
303
+
304
+ except Exception as e:
305
+ logger.error(f"Error en analyze_vocabulary_diversity: {str(e)}")
306
+ return 0.0, {}
307
+
308
+ def analyze_cohesion(doc):
309
+ """Analiza la cohesión textual"""
310
+ try:
311
+ sentences = list(doc.sents)
312
+ if len(sentences) < 2:
313
+ logger.warning("Texto demasiado corto para análisis de cohesión")
314
+ return 0.0
315
+
316
+ # 1. Análisis de conexiones léxicas
317
+ lexical_connections = 0
318
+ total_possible_connections = 0
319
+
320
+ for i in range(len(sentences)-1):
321
+ # Obtener lemmas significativos (no stopwords)
322
+ sent1_words = {token.lemma_ for token in sentences[i]
323
+ if token.is_alpha and not token.is_stop}
324
+ sent2_words = {token.lemma_ for token in sentences[i+1]
325
+ if token.is_alpha and not token.is_stop}
326
+
327
+ if sent1_words and sent2_words: # Verificar que ambos conjuntos no estén vacíos
328
+ intersection = len(sent1_words.intersection(sent2_words))
329
+ total_possible = min(len(sent1_words), len(sent2_words))
330
+
331
+ if total_possible > 0:
332
+ lexical_score = intersection / total_possible
333
+ lexical_connections += lexical_score
334
+ total_possible_connections += 1
335
+
336
+ # 2. Análisis de conectores
337
+ connector_count = 0
338
+ connector_types = {
339
+ 'CCONJ': 1.0, # Coordinantes
340
+ 'SCONJ': 1.2, # Subordinantes
341
+ 'ADV': 0.8 # Adverbios conectivos
342
+ }
343
+
344
+ for token in doc:
345
+ if (token.pos_ in connector_types and
346
+ token.dep_ in ['cc', 'mark', 'advmod'] and
347
+ not token.is_stop):
348
+ connector_count += connector_types[token.pos_]
349
+
350
+ # 3. Cálculo de scores normalizados
351
+ if total_possible_connections > 0:
352
+ lexical_cohesion = lexical_connections / total_possible_connections
353
+ else:
354
+ lexical_cohesion = 0
355
+
356
+ if len(sentences) > 1:
357
+ connector_cohesion = min(1.0, connector_count / (len(sentences) - 1))
358
+ else:
359
+ connector_cohesion = 0
360
+
361
+ # 4. Score final ponderado
362
+ weights = {
363
+ 'lexical': 0.7,
364
+ 'connectors': 0.3
365
+ }
366
+
367
+ cohesion_score = (
368
+ weights['lexical'] * lexical_cohesion +
369
+ weights['connectors'] * connector_cohesion
370
+ )
371
+
372
+ # 5. Logging para diagnóstico
373
+ logger.info(f"""
374
+ Análisis de Cohesión:
375
+ - Conexiones léxicas encontradas: {lexical_connections}
376
+ - Conexiones posibles: {total_possible_connections}
377
+ - Lexical cohesion score: {lexical_cohesion}
378
+ - Conectores encontrados: {connector_count}
379
+ - Connector cohesion score: {connector_cohesion}
380
+ - Score final: {cohesion_score}
381
+ """)
382
+
383
+ return cohesion_score
384
+
385
+ except Exception as e:
386
+ logger.error(f"Error en analyze_cohesion: {str(e)}")
387
+ return 0.0
388
+
389
+ def analyze_structure(doc):
390
+ try:
391
+ if len(doc) == 0:
392
+ return 0.0
393
+
394
+ structure_scores = []
395
+ for token in doc:
396
+ if token.dep_ == 'ROOT':
397
+ result = get_dependency_depths(token)
398
+ structure_scores.append(result['final_score'])
399
+
400
+ if not structure_scores:
401
+ return 0.0
402
+
403
+ return min(1.0, sum(structure_scores) / len(structure_scores))
404
+
405
+ except Exception as e:
406
+ logger.error(f"Error en analyze_structure: {str(e)}")
407
+ return 0.0
408
+
409
+ # Funciones auxiliares de análisis
410
+
411
+ def get_dependency_depths(token, depth=0, analyzed_tokens=None):
412
+ """
413
+ Analiza la profundidad y calidad de las relaciones de dependencia.
414
+
415
+ Args:
416
+ token: Token a analizar
417
+ depth: Profundidad actual en el árbol
418
+ analyzed_tokens: Set para evitar ciclos en el análisis
419
+
420
+ Returns:
421
+ dict: Información detallada sobre las dependencias
422
+ - depths: Lista de profundidades
423
+ - relations: Diccionario con tipos de relaciones encontradas
424
+ - complexity_score: Puntuación de complejidad
425
+ """
426
+ if analyzed_tokens is None:
427
+ analyzed_tokens = set()
428
+
429
+ # Evitar ciclos
430
+ if token.i in analyzed_tokens:
431
+ return {
432
+ 'depths': [],
433
+ 'relations': {},
434
+ 'complexity_score': 0
435
+ }
436
+
437
+ analyzed_tokens.add(token.i)
438
+
439
+ # Pesos para diferentes tipos de dependencias
440
+ dependency_weights = {
441
+ # Dependencias principales
442
+ 'nsubj': 1.2, # Sujeto nominal
443
+ 'obj': 1.1, # Objeto directo
444
+ 'iobj': 1.1, # Objeto indirecto
445
+ 'ROOT': 1.3, # Raíz
446
+
447
+ # Modificadores
448
+ 'amod': 0.8, # Modificador adjetival
449
+ 'advmod': 0.8, # Modificador adverbial
450
+ 'nmod': 0.9, # Modificador nominal
451
+
452
+ # Estructuras complejas
453
+ 'csubj': 1.4, # Cláusula como sujeto
454
+ 'ccomp': 1.3, # Complemento clausal
455
+ 'xcomp': 1.2, # Complemento clausal abierto
456
+ 'advcl': 1.2, # Cláusula adverbial
457
+
458
+ # Coordinación y subordinación
459
+ 'conj': 1.1, # Conjunción
460
+ 'cc': 0.7, # Coordinación
461
+ 'mark': 0.8, # Marcador
462
+
463
+ # Otros
464
+ 'det': 0.5, # Determinante
465
+ 'case': 0.5, # Caso
466
+ 'punct': 0.1 # Puntuación
467
+ }
468
+
469
+ # Inicializar resultados
470
+ current_result = {
471
+ 'depths': [depth],
472
+ 'relations': {token.dep_: 1},
473
+ 'complexity_score': dependency_weights.get(token.dep_, 0.5) * (depth + 1)
474
+ }
475
+
476
+ # Analizar hijos recursivamente
477
+ for child in token.children:
478
+ child_result = get_dependency_depths(child, depth + 1, analyzed_tokens)
479
+
480
+ # Combinar profundidades
481
+ current_result['depths'].extend(child_result['depths'])
482
+
483
+ # Combinar relaciones
484
+ for rel, count in child_result['relations'].items():
485
+ current_result['relations'][rel] = current_result['relations'].get(rel, 0) + count
486
+
487
+ # Acumular score de complejidad
488
+ current_result['complexity_score'] += child_result['complexity_score']
489
+
490
+ # Calcular métricas adicionales
491
+ current_result['max_depth'] = max(current_result['depths'])
492
+ current_result['avg_depth'] = sum(current_result['depths']) / len(current_result['depths'])
493
+ current_result['relation_diversity'] = len(current_result['relations'])
494
+
495
+ # Calcular score ponderado por tipo de estructura
496
+ structure_bonus = 0
497
+
498
+ # Bonus por estructuras complejas
499
+ if 'csubj' in current_result['relations'] or 'ccomp' in current_result['relations']:
500
+ structure_bonus += 0.3
501
+
502
+ # Bonus por coordinación balanceada
503
+ if 'conj' in current_result['relations'] and 'cc' in current_result['relations']:
504
+ structure_bonus += 0.2
505
+
506
+ # Bonus por modificación rica
507
+ if len(set(['amod', 'advmod', 'nmod']) & set(current_result['relations'])) >= 2:
508
+ structure_bonus += 0.2
509
+
510
+ current_result['final_score'] = (
511
+ current_result['complexity_score'] * (1 + structure_bonus)
512
+ )
513
+
514
+ return current_result
515
+
516
+ def normalize_score(value, metric_type,
517
+ min_threshold=0.0, target_threshold=1.0,
518
+ range_factor=2.0, optimal_length=None,
519
+ optimal_connections=None, optimal_depth=None):
520
+ """
521
+ Normaliza un valor considerando umbrales específicos por tipo de métrica.
522
+
523
+ Args:
524
+ value: Valor a normalizar
525
+ metric_type: Tipo de métrica ('vocabulary', 'structure', 'cohesion', 'clarity')
526
+ min_threshold: Valor mínimo aceptable
527
+ target_threshold: Valor objetivo
528
+ range_factor: Factor para ajustar el rango
529
+ optimal_length: Longitud óptima (opcional)
530
+ optimal_connections: Número óptimo de conexiones (opcional)
531
+ optimal_depth: Profundidad óptima de estructura (opcional)
532
+
533
+ Returns:
534
+ float: Valor normalizado entre 0 y 1
535
+ """
536
+ try:
537
+ # Definir umbrales por tipo de métrica
538
+ METRIC_THRESHOLDS = {
539
+ 'vocabulary': {
540
+ 'min': 0.60,
541
+ 'target': 0.75,
542
+ 'range_factor': 1.5
543
+ },
544
+ 'structure': {
545
+ 'min': 0.65,
546
+ 'target': 0.80,
547
+ 'range_factor': 1.8
548
+ },
549
+ 'cohesion': {
550
+ 'min': 0.55,
551
+ 'target': 0.70,
552
+ 'range_factor': 1.6
553
+ },
554
+ 'clarity': {
555
+ 'min': 0.60,
556
+ 'target': 0.75,
557
+ 'range_factor': 1.7
558
+ }
559
+ }
560
+
561
+ # Validar valores negativos o cero
562
+ if value < 0:
563
+ logger.warning(f"Valor negativo recibido: {value}")
564
+ return 0.0
565
+
566
+ # Manejar caso donde el valor es cero
567
+ if value == 0:
568
+ logger.warning("Valor cero recibido")
569
+ return 0.0
570
+
571
+ # Obtener umbrales específicos para el tipo de métrica
572
+ thresholds = METRIC_THRESHOLDS.get(metric_type, {
573
+ 'min': min_threshold,
574
+ 'target': target_threshold,
575
+ 'range_factor': range_factor
576
+ })
577
+
578
+ # Identificar el valor de referencia a usar
579
+ if optimal_depth is not None:
580
+ reference = optimal_depth
581
+ elif optimal_connections is not None:
582
+ reference = optimal_connections
583
+ elif optimal_length is not None:
584
+ reference = optimal_length
585
+ else:
586
+ reference = thresholds['target']
587
+
588
+ # Validar valor de referencia
589
+ if reference <= 0:
590
+ logger.warning(f"Valor de referencia inválido: {reference}")
591
+ return 0.0
592
+
593
+ # Calcular score basado en umbrales
594
+ if value < thresholds['min']:
595
+ # Valor por debajo del mínimo
596
+ score = (value / thresholds['min']) * 0.5 # Máximo 0.5 para valores bajo el mínimo
597
+ elif value < thresholds['target']:
598
+ # Valor entre mínimo y objetivo
599
+ range_size = thresholds['target'] - thresholds['min']
600
+ progress = (value - thresholds['min']) / range_size
601
+ score = 0.5 + (progress * 0.5) # Escala entre 0.5 y 1.0
602
+ else:
603
+ # Valor alcanza o supera el objetivo
604
+ score = 1.0
605
+
606
+ # Penalizar valores muy por encima del objetivo
607
+ if value > (thresholds['target'] * thresholds['range_factor']):
608
+ excess = (value - thresholds['target']) / (thresholds['target'] * thresholds['range_factor'])
609
+ score = max(0.7, 1.0 - excess) # No bajar de 0.7 para valores altos
610
+
611
+ # Asegurar que el resultado esté entre 0 y 1
612
+ return max(0.0, min(1.0, score))
613
+
614
+ except Exception as e:
615
+ logger.error(f"Error en normalize_score: {str(e)}")
616
+ return 0.0
617
+
618
+
619
+ # Funciones de generación de gráficos
620
+ def generate_sentence_graphs(doc):
621
+ """Genera visualizaciones de estructura de oraciones"""
622
+ fig, ax = plt.subplots(figsize=(10, 6))
623
+ # Implementar visualización
624
+ plt.close()
625
+ return fig
626
+
627
+ def generate_word_connections(doc):
628
+ """Genera red de conexiones de palabras"""
629
+ fig, ax = plt.subplots(figsize=(10, 6))
630
+ # Implementar visualización
631
+ plt.close()
632
+ return fig
633
+
634
+ def generate_connection_paths(doc):
635
+ """Genera patrones de conexión"""
636
+ fig, ax = plt.subplots(figsize=(10, 6))
637
+ # Implementar visualización
638
+ plt.close()
639
+ return fig
640
+
641
+ def create_vocabulary_network(doc):
642
+ """
643
+ Genera el grafo de red de vocabulario.
644
+ """
645
+ G = nx.Graph()
646
+
647
+ # Crear nodos para palabras significativas
648
+ words = [token.text.lower() for token in doc if token.is_alpha and not token.is_stop]
649
+ word_freq = Counter(words)
650
+
651
+ # Añadir nodos con tamaño basado en frecuencia
652
+ for word, freq in word_freq.items():
653
+ G.add_node(word, size=freq)
654
+
655
+ # Crear conexiones basadas en co-ocurrencia
656
+ window_size = 5
657
+ for i in range(len(words) - window_size):
658
+ window = words[i:i+window_size]
659
+ for w1, w2 in combinations(set(window), 2):
660
+ if G.has_edge(w1, w2):
661
+ G[w1][w2]['weight'] += 1
662
+ else:
663
+ G.add_edge(w1, w2, weight=1)
664
+
665
+ # Crear visualización
666
+ fig, ax = plt.subplots(figsize=(12, 8))
667
+ pos = nx.spring_layout(G)
668
+
669
+ # Dibujar nodos
670
+ nx.draw_networkx_nodes(G, pos,
671
+ node_size=[G.nodes[node]['size']*100 for node in G.nodes],
672
+ node_color='lightblue',
673
+ alpha=0.7)
674
+
675
+ # Dibujar conexiones
676
+ nx.draw_networkx_edges(G, pos,
677
+ width=[G[u][v]['weight']*0.5 for u,v in G.edges],
678
+ alpha=0.5)
679
+
680
+ # Añadir etiquetas
681
+ nx.draw_networkx_labels(G, pos)
682
+
683
+ plt.title("Red de Vocabulario")
684
+ plt.axis('off')
685
+ return fig
686
+
687
+ def create_syntax_complexity_graph(doc):
688
+ """
689
+ Genera el diagrama de arco de complejidad sintáctica.
690
+ Muestra la estructura de dependencias con colores basados en la complejidad.
691
+ """
692
+ try:
693
+ # Preparar datos para la visualización
694
+ sentences = list(doc.sents)
695
+ if not sentences:
696
+ return None
697
+
698
+ # Crear figura para el gráfico
699
+ fig, ax = plt.subplots(figsize=(12, len(sentences) * 2))
700
+
701
+ # Colores para diferentes niveles de profundidad
702
+ depth_colors = plt.cm.viridis(np.linspace(0, 1, 6))
703
+
704
+ y_offset = 0
705
+ max_x = 0
706
+
707
+ for sent in sentences:
708
+ words = [token.text for token in sent]
709
+ x_positions = range(len(words))
710
+ max_x = max(max_x, len(words))
711
+
712
+ # Dibujar palabras
713
+ plt.plot(x_positions, [y_offset] * len(words), 'k-', alpha=0.2)
714
+ plt.scatter(x_positions, [y_offset] * len(words), alpha=0)
715
+
716
+ # Añadir texto
717
+ for i, word in enumerate(words):
718
+ plt.annotate(word, (i, y_offset), xytext=(0, -10),
719
+ textcoords='offset points', ha='center')
720
+
721
+ # Dibujar arcos de dependencia
722
+ for token in sent:
723
+ if token.dep_ != "ROOT":
724
+ # Calcular profundidad de dependencia
725
+ depth = 0
726
+ current = token
727
+ while current.head != current:
728
+ depth += 1
729
+ current = current.head
730
+
731
+ # Determinar posiciones para el arco
732
+ start = token.i - sent[0].i
733
+ end = token.head.i - sent[0].i
734
+
735
+ # Altura del arco basada en la distancia entre palabras
736
+ height = 0.5 * abs(end - start)
737
+
738
+ # Color basado en la profundidad
739
+ color = depth_colors[min(depth, len(depth_colors)-1)]
740
+
741
+ # Crear arco
742
+ arc = patches.Arc((min(start, end) + abs(end - start)/2, y_offset),
743
+ width=abs(end - start),
744
+ height=height,
745
+ angle=0,
746
+ theta1=0,
747
+ theta2=180,
748
+ color=color,
749
+ alpha=0.6)
750
+ ax.add_patch(arc)
751
+
752
+ y_offset -= 2
753
+
754
+ # Configurar el gráfico
755
+ plt.xlim(-1, max_x)
756
+ plt.ylim(y_offset - 1, 1)
757
+ plt.axis('off')
758
+ plt.title("Complejidad Sintáctica")
759
+
760
+ return fig
761
+
762
+ except Exception as e:
763
+ logger.error(f"Error en create_syntax_complexity_graph: {str(e)}")
764
+ return None
765
+
766
+
767
+ def create_cohesion_heatmap(doc):
768
+ """Genera un mapa de calor que muestra la cohesión entre párrafos/oraciones."""
769
+ try:
770
+ sentences = list(doc.sents)
771
+ n_sentences = len(sentences)
772
+
773
+ if n_sentences < 2:
774
+ return None
775
+
776
+ similarity_matrix = np.zeros((n_sentences, n_sentences))
777
+
778
+ for i in range(n_sentences):
779
+ for j in range(n_sentences):
780
+ sent1_lemmas = {token.lemma_ for token in sentences[i]
781
+ if token.is_alpha and not token.is_stop}
782
+ sent2_lemmas = {token.lemma_ for token in sentences[j]
783
+ if token.is_alpha and not token.is_stop}
784
+
785
+ if sent1_lemmas and sent2_lemmas:
786
+ intersection = len(sent1_lemmas & sent2_lemmas) # Corregido aquí
787
+ union = len(sent1_lemmas | sent2_lemmas) # Y aquí
788
+ similarity_matrix[i, j] = intersection / union if union > 0 else 0
789
+
790
+ # Crear visualización
791
+ fig, ax = plt.subplots(figsize=(10, 8))
792
+
793
+ sns.heatmap(similarity_matrix,
794
+ cmap='YlOrRd',
795
+ square=True,
796
+ xticklabels=False,
797
+ yticklabels=False,
798
+ cbar_kws={'label': 'Cohesión'},
799
+ ax=ax)
800
+
801
+ plt.title("Mapa de Cohesión Textual")
802
+ plt.xlabel("Oraciones")
803
+ plt.ylabel("Oraciones")
804
+
805
+ plt.tight_layout()
806
+ return fig
807
+
808
+ except Exception as e:
809
+ logger.error(f"Error en create_cohesion_heatmap: {str(e)}")
810
+ return None
modules/studentact/current_situation_interface--FAIL.py ADDED
@@ -0,0 +1,608 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/studentact/current_situation_interface.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+
9
+ from ..database.current_situation_mongo_db import store_current_situation_result
10
+
11
+ from ..database.writing_progress_mongo_db import (
12
+ store_writing_baseline,
13
+ store_writing_progress,
14
+ get_writing_baseline,
15
+ get_writing_progress,
16
+ get_latest_writing_metrics
17
+ )
18
+
19
+ from .current_situation_analysis import (
20
+ analyze_text_dimensions,
21
+ analyze_clarity,
22
+ analyze_vocabulary_diversity,
23
+ analyze_cohesion,
24
+ analyze_structure,
25
+ get_dependency_depths,
26
+ normalize_score,
27
+ generate_sentence_graphs,
28
+ generate_word_connections,
29
+ generate_connection_paths,
30
+ create_vocabulary_network,
31
+ create_syntax_complexity_graph,
32
+ create_cohesion_heatmap
33
+ )
34
+
35
+ # Configuración del estilo de matplotlib para el gráfico de radar
36
+ plt.rcParams['font.family'] = 'sans-serif'
37
+ plt.rcParams['axes.grid'] = True
38
+ plt.rcParams['axes.spines.top'] = False
39
+ plt.rcParams['axes.spines.right'] = False
40
+
41
+ logger = logging.getLogger(__name__)
42
+ ####################################
43
+
44
+ TEXT_TYPES = {
45
+ 'academic_article': {
46
+ 'name': 'Artículo Académico',
47
+ 'thresholds': {
48
+ 'vocabulary': {'min': 0.70, 'target': 0.85},
49
+ 'structure': {'min': 0.75, 'target': 0.90},
50
+ 'cohesion': {'min': 0.65, 'target': 0.80},
51
+ 'clarity': {'min': 0.70, 'target': 0.85}
52
+ }
53
+ },
54
+ 'student_essay': {
55
+ 'name': 'Trabajo Universitario',
56
+ 'thresholds': {
57
+ 'vocabulary': {'min': 0.60, 'target': 0.75},
58
+ 'structure': {'min': 0.65, 'target': 0.80},
59
+ 'cohesion': {'min': 0.55, 'target': 0.70},
60
+ 'clarity': {'min': 0.60, 'target': 0.75}
61
+ }
62
+ },
63
+ 'general_communication': {
64
+ 'name': 'Comunicación General',
65
+ 'thresholds': {
66
+ 'vocabulary': {'min': 0.50, 'target': 0.65},
67
+ 'structure': {'min': 0.55, 'target': 0.70},
68
+ 'cohesion': {'min': 0.45, 'target': 0.60},
69
+ 'clarity': {'min': 0.50, 'target': 0.65}
70
+ }
71
+ }
72
+ }
73
+ ####################################
74
+
75
+ ANALYSIS_DIMENSION_MAPPING = {
76
+ 'morphosyntactic': {
77
+ 'primary': ['vocabulary', 'clarity'],
78
+ 'secondary': ['structure'],
79
+ 'tools': ['arc_diagrams', 'word_repetition']
80
+ },
81
+ 'semantic': {
82
+ 'primary': ['cohesion', 'structure'],
83
+ 'secondary': ['vocabulary'],
84
+ 'tools': ['concept_graphs', 'semantic_networks']
85
+ },
86
+ 'discourse': {
87
+ 'primary': ['cohesion', 'structure'],
88
+ 'secondary': ['clarity'],
89
+ 'tools': ['comparative_analysis']
90
+ }
91
+ }
92
+
93
+ ##############################################################################
94
+ # FUNCIÓN PRINCIPAL
95
+ ##############################################################################
96
+ def display_current_situation_interface(lang_code, nlp_models, t):
97
+ """
98
+ TAB:
99
+ - Expander con radio para tipo de texto
100
+ Contenedor-1 con expanders:
101
+ - Expander "Métricas de la línea base"
102
+ - Expander "Métricas de la iteración"
103
+ Contenedor-2 (2 columnas):
104
+ - Col1: Texto base
105
+ - Col2: Texto iteración
106
+ Al final, Recomendaciones en un expander (una sola “fila”).
107
+ """
108
+
109
+ # --- Inicializar session_state ---
110
+ if 'base_text' not in st.session_state:
111
+ st.session_state.base_text = ""
112
+ if 'iter_text' not in st.session_state:
113
+ st.session_state.iter_text = ""
114
+ if 'base_metrics' not in st.session_state:
115
+ st.session_state.base_metrics = {}
116
+ if 'iter_metrics' not in st.session_state:
117
+ st.session_state.iter_metrics = {}
118
+ if 'show_base' not in st.session_state:
119
+ st.session_state.show_base = False
120
+ if 'show_iter' not in st.session_state:
121
+ st.session_state.show_iter = False
122
+
123
+ # Creamos un tab
124
+ tabs = st.tabs(["Análisis de Texto"])
125
+ with tabs[0]:
126
+ # [1] Expander con radio para seleccionar tipo de texto
127
+ with st.expander("Selecciona el tipo de texto", expanded=True):
128
+ text_type = st.radio(
129
+ "¿Qué tipo de texto quieres analizar?",
130
+ options=list(TEXT_TYPES.keys()),
131
+ format_func=lambda x: TEXT_TYPES[x]['name'],
132
+ index=0
133
+ )
134
+ st.session_state.current_text_type = text_type
135
+
136
+ st.markdown("---")
137
+
138
+ # ---------------------------------------------------------------------
139
+ # CONTENEDOR-1: Expanders para métricas base e iteración
140
+ # ---------------------------------------------------------------------
141
+ with st.container():
142
+ # --- Expander para la línea base ---
143
+ with st.expander("Métricas de la línea base", expanded=False):
144
+ if st.session_state.show_base and st.session_state.base_metrics:
145
+ # Mostramos los valores reales
146
+ display_metrics_in_one_row(st.session_state.base_metrics, text_type)
147
+ else:
148
+ # Mostramos la maqueta vacía
149
+ display_empty_metrics_row()
150
+
151
+ # --- Expander para la iteración ---
152
+ with st.expander("Métricas de la iteración", expanded=False):
153
+ if st.session_state.show_iter and st.session_state.iter_metrics:
154
+ display_metrics_in_one_row(st.session_state.iter_metrics, text_type)
155
+ else:
156
+ display_empty_metrics_row()
157
+
158
+ st.markdown("---")
159
+
160
+ # ---------------------------------------------------------------------
161
+ # CONTENEDOR-2: 2 columnas (texto base | texto iteración)
162
+ # ---------------------------------------------------------------------
163
+ with st.container():
164
+ col_left, col_right = st.columns(2)
165
+
166
+ # Columna izquierda: Texto base
167
+ with col_left:
168
+ st.markdown("**Texto base**")
169
+ text_base = st.text_area(
170
+ label="",
171
+ value=st.session_state.base_text,
172
+ key="text_base_area",
173
+ placeholder="Pega aquí tu texto base",
174
+ )
175
+ if st.button("Analizar Base"):
176
+ with st.spinner("Analizando texto base..."):
177
+ doc = nlp_models[lang_code](text_base)
178
+ metrics = analyze_text_dimensions(doc)
179
+
180
+ st.session_state.base_text = text_base
181
+ st.session_state.base_metrics = metrics
182
+ st.session_state.show_base = True
183
+ # Al analizar base, reiniciamos la iteración
184
+ st.session_state.show_iter = False
185
+
186
+ # Columna derecha: Texto iteración
187
+ with col_right:
188
+ st.markdown("**Texto de iteración**")
189
+ text_iter = st.text_area(
190
+ label="",
191
+ value=st.session_state.iter_text,
192
+ key="text_iter_area",
193
+ placeholder="Edita y mejora tu texto...",
194
+ disabled=not st.session_state.show_base
195
+ )
196
+ if st.button("Analizar Iteración", disabled=not st.session_state.show_base):
197
+ with st.spinner("Analizando iteración..."):
198
+ doc = nlp_models[lang_code](text_iter)
199
+ metrics = analyze_text_dimensions(doc)
200
+
201
+ st.session_state.iter_text = text_iter
202
+ st.session_state.iter_metrics = metrics
203
+ st.session_state.show_iter = True
204
+
205
+ # ---------------------------------------------------------------------
206
+ # Recomendaciones al final en un expander (una sola “fila”)
207
+ # ---------------------------------------------------------------------
208
+ if st.session_state.show_iter:
209
+ with st.expander("Recomendaciones", expanded=False):
210
+ reco_list = []
211
+ for dimension, values in st.session_state.iter_metrics.items():
212
+ score = values['normalized_score']
213
+ target = TEXT_TYPES[text_type]['thresholds'][dimension]['target']
214
+ if score < target:
215
+ # Aquí, en lugar de get_dimension_suggestions, unificamos con:
216
+ suggestions = suggest_improvement_tools_list(dimension)
217
+ reco_list.extend(suggestions)
218
+
219
+ if reco_list:
220
+ # Todas en una sola línea
221
+ st.write(" | ".join(reco_list))
222
+ else:
223
+ st.info("¡No hay recomendaciones! Todas las métricas superan la meta.")
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+ #Funciones de visualización ##################################
232
+ ############################################################
233
+ # Funciones de visualización para las métricas
234
+ ############################################################
235
+
236
+ def display_metrics_in_one_row(metrics, text_type):
237
+ """
238
+ Muestra las cuatro dimensiones (Vocabulario, Estructura, Cohesión, Claridad)
239
+ en una sola línea, usando 4 columnas con ancho uniforme.
240
+ """
241
+ thresholds = TEXT_TYPES[text_type]['thresholds']
242
+ dimensions = ["vocabulary", "structure", "cohesion", "clarity"]
243
+
244
+ col1, col2, col3, col4 = st.columns([1,1,1,1])
245
+ cols = [col1, col2, col3, col4]
246
+
247
+ for dim, col in zip(dimensions, cols):
248
+ score = metrics[dim]['normalized_score']
249
+ target = thresholds[dim]['target']
250
+ min_val = thresholds[dim]['min']
251
+
252
+ if score < min_val:
253
+ status = "⚠️ Por mejorar"
254
+ color = "inverse"
255
+ elif score < target:
256
+ status = "📈 Aceptable"
257
+ color = "off"
258
+ else:
259
+ status = "✅ Óptimo"
260
+ color = "normal"
261
+
262
+ with col:
263
+ col.metric(
264
+ label=dim.capitalize(),
265
+ value=f"{score:.2f}",
266
+ delta=f"{status} (Meta: {target:.2f})",
267
+ delta_color=color,
268
+ border=True
269
+ )
270
+
271
+
272
+ # -------------------------------------------------------------------------
273
+ # Función que muestra una fila de 4 columnas “vacías”
274
+ # -------------------------------------------------------------------------
275
+ def display_empty_metrics_row():
276
+ """
277
+ Muestra una fila de 4 columnas vacías (Vocabulario, Estructura, Cohesión, Claridad).
278
+ Cada columna se dibuja con st.metric en blanco (“-”).
279
+ """
280
+ empty_cols = st.columns([1,1,1,1])
281
+ labels = ["Vocabulario", "Estructura", "Cohesión", "Claridad"]
282
+
283
+ for col, lbl in zip(empty_cols, labels):
284
+ with col:
285
+ col.metric(
286
+ label=lbl,
287
+ value="-",
288
+ delta="",
289
+ border=True
290
+ )
291
+
292
+
293
+
294
+ ####################################################################
295
+
296
+ def display_metrics_analysis(metrics, text_type=None):
297
+ """
298
+ Muestra los resultados del análisis: métricas verticalmente y gráfico radar.
299
+ """
300
+ try:
301
+ # Usar valor por defecto si no se especifica tipo
302
+ text_type = text_type or 'student_essay'
303
+
304
+ # Obtener umbrales según el tipo de texto
305
+ thresholds = TEXT_TYPES[text_type]['thresholds']
306
+
307
+ # Crear dos columnas para las métricas y el gráfico
308
+ metrics_col, graph_col = st.columns([1, 1.5])
309
+
310
+ # Columna de métricas
311
+ with metrics_col:
312
+ metrics_config = [
313
+ {
314
+ 'label': "Vocabulario",
315
+ 'key': 'vocabulary',
316
+ 'value': metrics['vocabulary']['normalized_score'],
317
+ 'help': "Riqueza y variedad del vocabulario",
318
+ 'thresholds': thresholds['vocabulary']
319
+ },
320
+ {
321
+ 'label': "Estructura",
322
+ 'key': 'structure',
323
+ 'value': metrics['structure']['normalized_score'],
324
+ 'help': "Organización y complejidad de oraciones",
325
+ 'thresholds': thresholds['structure']
326
+ },
327
+ {
328
+ 'label': "Cohesión",
329
+ 'key': 'cohesion',
330
+ 'value': metrics['cohesion']['normalized_score'],
331
+ 'help': "Conexión y fluidez entre ideas",
332
+ 'thresholds': thresholds['cohesion']
333
+ },
334
+ {
335
+ 'label': "Claridad",
336
+ 'key': 'clarity',
337
+ 'value': metrics['clarity']['normalized_score'],
338
+ 'help': "Facilidad de comprensión del texto",
339
+ 'thresholds': thresholds['clarity']
340
+ }
341
+ ]
342
+
343
+ # Mostrar métricas
344
+ for metric in metrics_config:
345
+ value = metric['value']
346
+ if value < metric['thresholds']['min']:
347
+ status = "⚠️ Por mejorar"
348
+ color = "inverse"
349
+ elif value < metric['thresholds']['target']:
350
+ status = "📈 Aceptable"
351
+ color = "off"
352
+ else:
353
+ status = "✅ Óptimo"
354
+ color = "normal"
355
+
356
+ st.metric(
357
+ metric['label'],
358
+ f"{value:.2f}",
359
+ f"{status} (Meta: {metric['thresholds']['target']:.2f})",
360
+ delta_color=color,
361
+ help=metric['help']
362
+ )
363
+ st.markdown("<div style='margin-bottom: 0.5rem;'></div>", unsafe_allow_html=True)
364
+
365
+ except Exception as e:
366
+ logger.error(f"Error mostrando resultados: {str(e)}")
367
+ st.error("Error al mostrar los resultados")
368
+
369
+ def display_comparison_results(baseline_metrics, current_metrics):
370
+ """Muestra comparación entre línea base y métricas actuales"""
371
+
372
+ # Crear columnas para métricas y gráfico
373
+ metrics_col, graph_col = st.columns([1, 1.5])
374
+
375
+ with metrics_col:
376
+ for dimension in ['vocabulary', 'structure', 'cohesion', 'clarity']:
377
+ baseline = baseline_metrics[dimension]['normalized_score']
378
+ current = current_metrics[dimension]['normalized_score']
379
+ delta = current - baseline
380
+
381
+ st.metric(
382
+ dimension.title(),
383
+ f"{current:.2f}",
384
+ f"{delta:+.2f}",
385
+ delta_color="normal" if delta >= 0 else "inverse"
386
+ )
387
+
388
+ # Sugerir herramientas de mejora
389
+ if delta < 0:
390
+ suggest_improvement_tools(dimension)
391
+
392
+ with graph_col:
393
+ display_radar_chart_comparison(
394
+ baseline_metrics,
395
+ current_metrics
396
+ )
397
+
398
+ def display_metrics_and_suggestions(metrics, text_type, title, show_suggestions=False):
399
+ """
400
+ Muestra métricas y opcionalmente sugerencias de mejora.
401
+ Args:
402
+ metrics: Diccionario con las métricas analizadas
403
+ text_type: Tipo de texto seleccionado
404
+ title: Título para las métricas ("Base" o "Iteración")
405
+ show_suggestions: Booleano para mostrar sugerencias
406
+ """
407
+ try:
408
+ thresholds = TEXT_TYPES[text_type]['thresholds']
409
+
410
+ st.markdown(f"### Métricas {title}")
411
+
412
+ for dimension, values in metrics.items():
413
+ score = values['normalized_score']
414
+ target = thresholds[dimension]['target']
415
+ min_val = thresholds[dimension]['min']
416
+
417
+ # Determinar estado y color
418
+ if score < min_val:
419
+ status = "⚠️ Por mejorar"
420
+ color = "inverse"
421
+ elif score < target:
422
+ status = "📈 Aceptable"
423
+ color = "off"
424
+ else:
425
+ status = "✅ Óptimo"
426
+ color = "normal"
427
+
428
+ # Mostrar métrica
429
+ st.metric(
430
+ dimension.title(),
431
+ f"{score:.2f}",
432
+ f"{status} (Meta: {target:.2f})",
433
+ delta_color=color,
434
+ help=f"Meta: {target:.2f}, Mínimo: {min_val:.2f}"
435
+ )
436
+
437
+ # Mostrar sugerencias si es necesario
438
+ if show_suggestions and score < target:
439
+ suggest_improvement_tools(dimension)
440
+
441
+ # Agregar espacio entre métricas
442
+ st.markdown("<div style='margin-bottom: 0.5rem;'></div>", unsafe_allow_html=True)
443
+
444
+ except Exception as e:
445
+ logger.error(f"Error mostrando métricas: {str(e)}")
446
+ st.error("Error al mostrar métricas")
447
+
448
+ def display_radar_chart(metrics_config, thresholds, baseline_metrics=None):
449
+ """
450
+ Muestra el gráfico radar con los resultados.
451
+ Args:
452
+ metrics_config: Configuración actual de métricas
453
+ thresholds: Umbrales para las métricas
454
+ baseline_metrics: Métricas de línea base (opcional)
455
+ """
456
+ try:
457
+ # Preparar datos para el gráfico
458
+ categories = [m['label'] for m in metrics_config]
459
+ values_current = [m['value'] for m in metrics_config]
460
+ min_values = [m['thresholds']['min'] for m in metrics_config]
461
+ target_values = [m['thresholds']['target'] for m in metrics_config]
462
+
463
+ # Crear y configurar gráfico
464
+ fig = plt.figure(figsize=(8, 8))
465
+ ax = fig.add_subplot(111, projection='polar')
466
+
467
+ # Configurar radar
468
+ angles = [n / float(len(categories)) * 2 * np.pi for n in range(len(categories))]
469
+ angles += angles[:1]
470
+ values_current += values_current[:1]
471
+ min_values += min_values[:1]
472
+ target_values += target_values[:1]
473
+
474
+ # Configurar ejes
475
+ ax.set_xticks(angles[:-1])
476
+ ax.set_xticklabels(categories, fontsize=10)
477
+ circle_ticks = np.arange(0, 1.1, 0.2)
478
+ ax.set_yticks(circle_ticks)
479
+ ax.set_yticklabels([f'{tick:.1f}' for tick in circle_ticks], fontsize=8)
480
+ ax.set_ylim(0, 1)
481
+
482
+ # Dibujar áreas de umbrales
483
+ ax.plot(angles, min_values, '#e74c3c', linestyle='--', linewidth=1,
484
+ label='Mínimo', alpha=0.5)
485
+ ax.plot(angles, target_values, '#2ecc71', linestyle='--', linewidth=1,
486
+ label='Meta', alpha=0.5)
487
+ ax.fill_between(angles, target_values, [1]*len(angles),
488
+ color='#2ecc71', alpha=0.1)
489
+ ax.fill_between(angles, [0]*len(angles), min_values,
490
+ color='#e74c3c', alpha=0.1)
491
+
492
+ # Si hay línea base, dibujarla primero
493
+ if baseline_metrics is not None:
494
+ values_baseline = [baseline_metrics[m['key']]['normalized_score']
495
+ for m in metrics_config]
496
+ values_baseline += values_baseline[:1]
497
+ ax.plot(angles, values_baseline, '#888888', linewidth=2,
498
+ label='Línea base', linestyle='--')
499
+ ax.fill(angles, values_baseline, '#888888', alpha=0.1)
500
+
501
+ # Dibujar valores actuales
502
+ label = 'Actual' if baseline_metrics else 'Tu escritura'
503
+ color = '#3498db' if baseline_metrics else '#3498db'
504
+
505
+ ax.plot(angles, values_current, color, linewidth=2, label=label)
506
+ ax.fill(angles, values_current, color, alpha=0.2)
507
+
508
+ # Ajustar leyenda
509
+ legend_handles = []
510
+ if baseline_metrics:
511
+ legend_handles.extend([
512
+ plt.Line2D([], [], color='#888888', linestyle='--',
513
+ label='Línea base'),
514
+ plt.Line2D([], [], color='#3498db', label='Actual')
515
+ ])
516
+ else:
517
+ legend_handles.extend([
518
+ plt.Line2D([], [], color='#3498db', label='Tu escritura')
519
+ ])
520
+
521
+ legend_handles.extend([
522
+ plt.Line2D([], [], color='#e74c3c', linestyle='--', label='Mínimo'),
523
+ plt.Line2D([], [], color='#2ecc71', linestyle='--', label='Meta')
524
+ ])
525
+
526
+ ax.legend(
527
+ handles=legend_handles,
528
+ loc='upper right',
529
+ bbox_to_anchor=(1.3, 1.1),
530
+ fontsize=10,
531
+ frameon=True,
532
+ facecolor='white',
533
+ edgecolor='none',
534
+ shadow=True
535
+ )
536
+
537
+ plt.tight_layout()
538
+ st.pyplot(fig)
539
+ plt.close()
540
+
541
+ except Exception as e:
542
+ logger.error(f"Error mostrando gráfico radar: {str(e)}")
543
+ st.error("Error al mostrar el gráfico")
544
+
545
+ #Funciones auxiliares ##################################
546
+
547
+
548
+ ############################################################
549
+ # Unificamos la lógica de sugerencias en una función
550
+ ############################################################
551
+ def suggest_improvement_tools_list(dimension):
552
+ """
553
+ Retorna en forma de lista las herramientas sugeridas
554
+ basadas en 'ANALYSIS_DIMENSION_MAPPING'.
555
+ """
556
+ suggestions = []
557
+ for analysis, mapping in ANALYSIS_DIMENSION_MAPPING.items():
558
+ # Verificamos si la dimensión está en primary o secondary
559
+ if dimension in mapping['primary'] or dimension in mapping['secondary']:
560
+ suggestions.extend(mapping['tools'])
561
+ # Si no hay nada, al menos retornamos un placeholder
562
+ return suggestions if suggestions else ["Sin sugerencias específicas."]
563
+
564
+
565
+ def prepare_metrics_config(metrics, text_type='student_essay'):
566
+ """
567
+ Prepara la configuración de métricas en el mismo formato que display_results.
568
+ Args:
569
+ metrics: Diccionario con las métricas analizadas
570
+ text_type: Tipo de texto para los umbrales
571
+ Returns:
572
+ list: Lista de configuraciones de métricas
573
+ """
574
+ # Obtener umbrales según el tipo de texto
575
+ thresholds = TEXT_TYPES[text_type]['thresholds']
576
+
577
+ # Usar la misma estructura que en display_results
578
+ return [
579
+ {
580
+ 'label': "Vocabulario",
581
+ 'key': 'vocabulary',
582
+ 'value': metrics['vocabulary']['normalized_score'],
583
+ 'help': "Riqueza y variedad del vocabulario",
584
+ 'thresholds': thresholds['vocabulary']
585
+ },
586
+ {
587
+ 'label': "Estructura",
588
+ 'key': 'structure',
589
+ 'value': metrics['structure']['normalized_score'],
590
+ 'help': "Organización y complejidad de oraciones",
591
+ 'thresholds': thresholds['structure']
592
+ },
593
+ {
594
+ 'label': "Cohesión",
595
+ 'key': 'cohesion',
596
+ 'value': metrics['cohesion']['normalized_score'],
597
+ 'help': "Conexión y fluidez entre ideas",
598
+ 'thresholds': thresholds['cohesion']
599
+ },
600
+ {
601
+ 'label': "Claridad",
602
+ 'key': 'clarity',
603
+ 'value': metrics['clarity']['normalized_score'],
604
+ 'help': "Facilidad de comprensión del texto",
605
+ 'thresholds': thresholds['clarity']
606
+ }
607
+ ]
608
+
modules/studentact/current_situation_interface-v1.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/studentact/current_situation_interface.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+ from .current_situation_analysis import (
7
+ analyze_text_dimensions,
8
+ analyze_clarity,
9
+ analyze_reference_clarity,
10
+ analyze_vocabulary_diversity,
11
+ analyze_cohesion,
12
+ analyze_structure,
13
+ get_dependency_depths,
14
+ normalize_score,
15
+ generate_sentence_graphs,
16
+ generate_word_connections,
17
+ generate_connection_paths,
18
+ create_vocabulary_network,
19
+ create_syntax_complexity_graph,
20
+ create_cohesion_heatmap,
21
+ )
22
+
23
+ logger = logging.getLogger(__name__)
24
+ ####################################
25
+ def display_current_situation_interface(lang_code, nlp_models, t):
26
+ """
27
+ Interfaz simplificada para el análisis inicial, enfocada en recomendaciones directas.
28
+ """
29
+ # Inicializar estados si no existen
30
+ if 'text_input' not in st.session_state:
31
+ st.session_state.text_input = ""
32
+ if 'show_results' not in st.session_state:
33
+ st.session_state.show_results = False
34
+ if 'current_doc' not in st.session_state:
35
+ st.session_state.current_doc = None
36
+ if 'current_metrics' not in st.session_state:
37
+ st.session_state.current_metrics = None
38
+
39
+ st.markdown("## Análisis Inicial de Escritura")
40
+
41
+ # Container principal con dos columnas
42
+ with st.container():
43
+ input_col, results_col = st.columns([1,2])
44
+
45
+ with input_col:
46
+ st.markdown("### Ingresa tu texto")
47
+
48
+ # Función para manejar cambios en el texto
49
+ def on_text_change():
50
+ st.session_state.text_input = st.session_state.text_area
51
+ st.session_state.show_results = False # Resetear resultados cuando el texto cambia
52
+
53
+ # Text area con manejo de estado
54
+ text_input = st.text_area(
55
+ t.get('input_prompt', "Escribe o pega tu texto aquí:"),
56
+ height=400,
57
+ key="text_area",
58
+ value=st.session_state.text_input,
59
+ on_change=on_text_change,
60
+ help="Este texto será analizado para darte recomendaciones personalizadas"
61
+ )
62
+
63
+ # Botón de análisis
64
+ if st.button(
65
+ t.get('analyze_button', "Analizar mi escritura"),
66
+ type="primary",
67
+ disabled=not text_input.strip(),
68
+ use_container_width=True,
69
+ ):
70
+ try:
71
+ with st.spinner(t.get('processing', "Analizando...")):
72
+ # Procesar texto y obtener métricas
73
+ doc = nlp_models[lang_code](text_input)
74
+ metrics = analyze_text_dimensions(doc)
75
+
76
+ # Actualizar estado con nuevos resultados
77
+ st.session_state.current_doc = doc
78
+ st.session_state.current_metrics = metrics
79
+ st.session_state.show_results = True
80
+
81
+ # Mantener el texto en el estado
82
+ st.session_state.text_input = text_input
83
+
84
+ except Exception as e:
85
+ logger.error(f"Error en análisis: {str(e)}")
86
+ st.error(t.get('analysis_error', "Error al analizar el texto"))
87
+
88
+ # Mostrar resultados en la columna derecha
89
+ with results_col:
90
+ if st.session_state.show_results and st.session_state.current_metrics is not None:
91
+ display_recommendations(st.session_state.current_metrics, t)
92
+
93
+ # Opción para ver detalles
94
+ with st.expander("🔍 Ver análisis detallado", expanded=False):
95
+ display_current_situation_visual(
96
+ st.session_state.current_doc,
97
+ st.session_state.current_metrics
98
+ )
99
+
100
+ def display_current_situation_visual(doc, metrics):
101
+ """
102
+ Muestra visualizaciones detalladas del análisis.
103
+ """
104
+ try:
105
+ st.markdown("### 📊 Visualizaciones Detalladas")
106
+
107
+ # 1. Visualización de vocabulario
108
+ with st.expander("Análisis de Vocabulario", expanded=True):
109
+ vocab_graph = create_vocabulary_network(doc)
110
+ if vocab_graph:
111
+ st.pyplot(vocab_graph)
112
+ plt.close(vocab_graph)
113
+
114
+ # 2. Visualización de estructura
115
+ with st.expander("Análisis de Estructura", expanded=True):
116
+ syntax_graph = create_syntax_complexity_graph(doc)
117
+ if syntax_graph:
118
+ st.pyplot(syntax_graph)
119
+ plt.close(syntax_graph)
120
+
121
+ # 3. Visualización de cohesión
122
+ with st.expander("Análisis de Cohesión", expanded=True):
123
+ cohesion_graph = create_cohesion_heatmap(doc)
124
+ if cohesion_graph:
125
+ st.pyplot(cohesion_graph)
126
+ plt.close(cohesion_graph)
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error en visualización: {str(e)}")
130
+ st.error("Error al generar las visualizaciones")
131
+
132
+
133
+ ####################################
134
+ def display_recommendations(metrics, t):
135
+ """
136
+ Muestra recomendaciones basadas en las métricas del texto.
137
+ """
138
+ # 1. Resumen Visual con Explicación
139
+ st.markdown("### 📊 Resumen de tu Análisis")
140
+
141
+ # Explicación del sistema de medición
142
+ st.markdown("""
143
+ **¿Cómo interpretar los resultados?**
144
+
145
+ Cada métrica se mide en una escala de 0.0 a 1.0, donde:
146
+ - 0.0 - 0.4: Necesita atención prioritaria
147
+ - 0.4 - 0.6: En desarrollo
148
+ - 0.6 - 0.8: Buen nivel
149
+ - 0.8 - 1.0: Nivel avanzado
150
+ """)
151
+
152
+ # Métricas con explicaciones detalladas
153
+ col1, col2, col3, col4 = st.columns(4)
154
+
155
+ with col1:
156
+ st.metric(
157
+ "Vocabulario",
158
+ f"{metrics['vocabulary']['normalized_score']:.2f}",
159
+ help="Mide la variedad y riqueza de tu vocabulario. Un valor alto indica un uso diverso de palabras sin repeticiones excesivas."
160
+ )
161
+ with st.expander("ℹ️ Detalles"):
162
+ st.write("""
163
+ **Vocabulario**
164
+ - Evalúa la diversidad léxica
165
+ - Considera palabras únicas vs. totales
166
+ - Detecta repeticiones innecesarias
167
+ - Valor óptimo: > 0.7
168
+ """)
169
+
170
+ with col2:
171
+ st.metric(
172
+ "Estructura",
173
+ f"{metrics['structure']['normalized_score']:.2f}",
174
+ help="Evalúa la complejidad y variedad de las estructuras sintácticas en tus oraciones."
175
+ )
176
+ with st.expander("ℹ️ Detalles"):
177
+ st.write("""
178
+ **Estructura**
179
+ - Analiza la complejidad sintáctica
180
+ - Mide variación en construcciones
181
+ - Evalúa longitud de oraciones
182
+ - Valor óptimo: > 0.6
183
+ """)
184
+
185
+ with col3:
186
+ st.metric(
187
+ "Cohesión",
188
+ f"{metrics['cohesion']['normalized_score']:.2f}",
189
+ help="Indica qué tan bien conectadas están tus ideas y párrafos entre sí."
190
+ )
191
+ with st.expander("ℹ️ Detalles"):
192
+ st.write("""
193
+ **Cohesión**
194
+ - Mide conexiones entre ideas
195
+ - Evalúa uso de conectores
196
+ - Analiza progresión temática
197
+ - Valor óptimo: > 0.65
198
+ """)
199
+
200
+ with col4:
201
+ st.metric(
202
+ "Claridad",
203
+ f"{metrics['clarity']['normalized_score']:.2f}",
204
+ help="Evalúa la facilidad de comprensión general de tu texto."
205
+ )
206
+ with st.expander("ℹ️ Detalles"):
207
+ st.write("""
208
+ **Claridad**
209
+ - Evalúa comprensibilidad
210
+ - Considera estructura lógica
211
+ - Mide precisión expresiva
212
+ - Valor óptimo: > 0.7
213
+ """)
214
+
215
+ st.markdown("---")
216
+
217
+ # 2. Recomendaciones basadas en puntuaciones
218
+ st.markdown("### 💡 Recomendaciones Personalizadas")
219
+
220
+ # Recomendaciones morfosintácticas
221
+ if metrics['structure']['normalized_score'] < 0.6:
222
+ st.warning("""
223
+ #### 📝 Análisis Morfosintáctico Recomendado
224
+
225
+ **Tu nivel actual sugiere que sería beneficioso:**
226
+ 1. Realizar el análisis morfosintáctico de 3 párrafos diferentes
227
+ 2. Practicar la combinación de oraciones simples en compuestas
228
+ 3. Identificar y clasificar tipos de oraciones en textos académicos
229
+ 4. Ejercitar la variación sintáctica
230
+
231
+ *Hacer clic en "Comenzar ejercicios" para acceder al módulo morfosintáctico*
232
+ """)
233
+
234
+ # Recomendaciones semánticas
235
+ if metrics['vocabulary']['normalized_score'] < 0.7:
236
+ st.warning("""
237
+ #### 📚 Análisis Semántico Recomendado
238
+
239
+ **Para mejorar tu vocabulario y expresión:**
240
+ A. Realiza el análisis semántico de un texto académico
241
+ B. Identifica y agrupa campos semánticos relacionados
242
+ C. Practica la sustitución léxica en tus párrafos
243
+ D. Construye redes de conceptos sobre tu tema
244
+ E. Analiza las relaciones entre ideas principales
245
+
246
+ *Hacer clic en "Comenzar ejercicios" para acceder al módulo semántico*
247
+ """)
248
+
249
+ # Recomendaciones de cohesión
250
+ if metrics['cohesion']['normalized_score'] < 0.65:
251
+ st.warning("""
252
+ #### 🔄 Análisis del Discurso Recomendado
253
+
254
+ **Para mejorar la conexión entre ideas:**
255
+ 1. Realizar el análisis del discurso de un texto modelo
256
+ 2. Practicar el uso de diferentes conectores textuales
257
+ 3. Identificar cadenas de referencia en textos académicos
258
+ 4. Ejercitar la progresión temática en tus escritos
259
+
260
+ *Hacer clic en "Comenzar ejercicios" para acceder al módulo de análisis del discurso*
261
+ """)
262
+
263
+ # Botón de acción
264
+ st.markdown("---")
265
+ col1, col2, col3 = st.columns([1,2,1])
266
+ with col2:
267
+ st.button(
268
+ "🎯 Comenzar ejercicios recomendados",
269
+ type="primary",
270
+ use_container_width=True,
271
+ key="start_exercises"
272
+ )
modules/studentact/current_situation_interface-v2.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/studentact/current_situation_interface.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+
7
+ from ..database.current_situation_mongo_db import store_current_situation_result
8
+
9
+ from .current_situation_analysis import (
10
+ analyze_text_dimensions,
11
+ analyze_clarity,
12
+ analyze_reference_clarity,
13
+ analyze_vocabulary_diversity,
14
+ analyze_cohesion,
15
+ analyze_structure,
16
+ get_dependency_depths,
17
+ normalize_score,
18
+ generate_sentence_graphs,
19
+ generate_word_connections,
20
+ generate_connection_paths,
21
+ create_vocabulary_network,
22
+ create_syntax_complexity_graph,
23
+ create_cohesion_heatmap,
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+ ####################################
28
+
29
+ def display_current_situation_interface(lang_code, nlp_models, t):
30
+ """
31
+ Interfaz simplificada para el análisis inicial, enfocada en recomendaciones directas.
32
+ """
33
+ try:
34
+ # Inicializar estados si no existen
35
+ if 'text_input' not in st.session_state:
36
+ st.session_state.text_input = ""
37
+ if 'show_results' not in st.session_state:
38
+ st.session_state.show_results = False
39
+ if 'current_doc' not in st.session_state:
40
+ st.session_state.current_doc = None
41
+ if 'current_metrics' not in st.session_state:
42
+ st.session_state.current_metrics = None
43
+
44
+ st.markdown("## Análisis Inicial de Escritura")
45
+
46
+ # Container principal con dos columnas
47
+ with st.container():
48
+ input_col, results_col = st.columns([1,2])
49
+
50
+ with input_col:
51
+ st.markdown("### Ingresa tu texto")
52
+
53
+ # Función para manejar cambios en el texto
54
+ def on_text_change():
55
+ st.session_state.text_input = st.session_state.text_area
56
+ st.session_state.show_results = False # Resetear resultados cuando el texto cambia
57
+
58
+ # Text area con manejo de estado
59
+ text_input = st.text_area(
60
+ t.get('input_prompt', "Escribe o pega tu texto aquí:"),
61
+ height=400,
62
+ key="text_area",
63
+ value=st.session_state.text_input,
64
+ on_change=on_text_change,
65
+ help="Este texto será analizado para darte recomendaciones personalizadas"
66
+ )
67
+
68
+ if st.button(
69
+ t.get('analyze_button', "Analizar mi escritura"),
70
+ type="primary",
71
+ disabled=not text_input.strip(),
72
+ use_container_width=True,
73
+ ):
74
+ try:
75
+ with st.spinner(t.get('processing', "Analizando...")):
76
+ # Procesar texto y obtener métricas
77
+ doc = nlp_models[lang_code](text_input)
78
+ metrics = analyze_text_dimensions(doc)
79
+
80
+ # Guardar en MongoDB
81
+ storage_success = store_current_situation_result(
82
+ username=st.session_state.username,
83
+ text=text_input,
84
+ metrics=metrics,
85
+ feedback=None # Por ahora sin feedback
86
+ )
87
+
88
+ if not storage_success:
89
+ logger.warning("No se pudo guardar el análisis en la base de datos")
90
+
91
+ # Actualizar estado
92
+ st.session_state.current_doc = doc
93
+ st.session_state.current_metrics = metrics
94
+ st.session_state.show_results = True
95
+ st.session_state.text_input = text_input
96
+
97
+ except Exception as e:
98
+ logger.error(f"Error en análisis: {str(e)}")
99
+ st.error(t.get('analysis_error', "Error al analizar el texto"))
100
+
101
+ # Mostrar resultados en la columna derecha
102
+ with results_col:
103
+ if st.session_state.show_results and st.session_state.current_metrics is not None:
104
+ display_recommendations(st.session_state.current_metrics, t)
105
+
106
+ # Opción para ver detalles
107
+ with st.expander("🔍 Ver análisis detallado", expanded=False):
108
+ display_current_situation_visual(
109
+ st.session_state.current_doc,
110
+ st.session_state.current_metrics
111
+ )
112
+
113
+ except Exception as e:
114
+ logger.error(f"Error en interfaz: {str(e)}")
115
+ st.error("Ocurrió un error. Por favor, intente de nuevo.")
116
+
117
+
118
+
119
+ def display_current_situation_visual(doc, metrics):
120
+ """
121
+ Muestra visualizaciones detalladas del análisis.
122
+ """
123
+ try:
124
+ st.markdown("### 📊 Visualizaciones Detalladas")
125
+
126
+ # 1. Visualización de vocabulario
127
+ with st.expander("Análisis de Vocabulario", expanded=True):
128
+ vocab_graph = create_vocabulary_network(doc)
129
+ if vocab_graph:
130
+ st.pyplot(vocab_graph)
131
+ plt.close(vocab_graph)
132
+
133
+ # 2. Visualización de estructura
134
+ with st.expander("Análisis de Estructura", expanded=True):
135
+ syntax_graph = create_syntax_complexity_graph(doc)
136
+ if syntax_graph:
137
+ st.pyplot(syntax_graph)
138
+ plt.close(syntax_graph)
139
+
140
+ # 3. Visualización de cohesión
141
+ with st.expander("Análisis de Cohesión", expanded=True):
142
+ cohesion_graph = create_cohesion_heatmap(doc)
143
+ if cohesion_graph:
144
+ st.pyplot(cohesion_graph)
145
+ plt.close(cohesion_graph)
146
+
147
+ except Exception as e:
148
+ logger.error(f"Error en visualización: {str(e)}")
149
+ st.error("Error al generar las visualizaciones")
150
+
151
+
152
+ ####################################
153
+ def display_recommendations(metrics, t):
154
+ """
155
+ Muestra recomendaciones basadas en las métricas del texto.
156
+ """
157
+ # 1. Resumen Visual con Explicación
158
+ st.markdown("### 📊 Resumen de tu Análisis")
159
+
160
+ # Explicación del sistema de medición
161
+ st.markdown("""
162
+ **¿Cómo interpretar los resultados?**
163
+
164
+ Cada métrica se mide en una escala de 0.0 a 1.0, donde:
165
+ - 0.0 - 0.4: Necesita atención prioritaria
166
+ - 0.4 - 0.6: En desarrollo
167
+ - 0.6 - 0.8: Buen nivel
168
+ - 0.8 - 1.0: Nivel avanzado
169
+ """)
170
+
171
+ # Métricas con explicaciones detalladas
172
+ col1, col2, col3, col4 = st.columns(4)
173
+
174
+ with col1:
175
+ st.metric(
176
+ "Vocabulario",
177
+ f"{metrics['vocabulary']['normalized_score']:.2f}",
178
+ help="Mide la variedad y riqueza de tu vocabulario. Un valor alto indica un uso diverso de palabras sin repeticiones excesivas."
179
+ )
180
+ with st.expander("ℹ️ Detalles"):
181
+ st.write("""
182
+ **Vocabulario**
183
+ - Evalúa la diversidad léxica
184
+ - Considera palabras únicas vs. totales
185
+ - Detecta repeticiones innecesarias
186
+ - Valor óptimo: > 0.7
187
+ """)
188
+
189
+ with col2:
190
+ st.metric(
191
+ "Estructura",
192
+ f"{metrics['structure']['normalized_score']:.2f}",
193
+ help="Evalúa la complejidad y variedad de las estructuras sintácticas en tus oraciones."
194
+ )
195
+ with st.expander("ℹ️ Detalles"):
196
+ st.write("""
197
+ **Estructura**
198
+ - Analiza la complejidad sintáctica
199
+ - Mide variación en construcciones
200
+ - Evalúa longitud de oraciones
201
+ - Valor óptimo: > 0.6
202
+ """)
203
+
204
+ with col3:
205
+ st.metric(
206
+ "Cohesión",
207
+ f"{metrics['cohesion']['normalized_score']:.2f}",
208
+ help="Indica qué tan bien conectadas están tus ideas y párrafos entre sí."
209
+ )
210
+ with st.expander("ℹ️ Detalles"):
211
+ st.write("""
212
+ **Cohesión**
213
+ - Mide conexiones entre ideas
214
+ - Evalúa uso de conectores
215
+ - Analiza progresión temática
216
+ - Valor óptimo: > 0.65
217
+ """)
218
+
219
+ with col4:
220
+ st.metric(
221
+ "Claridad",
222
+ f"{metrics['clarity']['normalized_score']:.2f}",
223
+ help="Evalúa la facilidad de comprensión general de tu texto."
224
+ )
225
+ with st.expander("ℹ️ Detalles"):
226
+ st.write("""
227
+ **Claridad**
228
+ - Evalúa comprensibilidad
229
+ - Considera estructura lógica
230
+ - Mide precisión expresiva
231
+ - Valor óptimo: > 0.7
232
+ """)
233
+
234
+ st.markdown("---")
235
+
236
+ # 2. Recomendaciones basadas en puntuaciones
237
+ st.markdown("### 💡 Recomendaciones Personalizadas")
238
+
239
+ # Recomendaciones morfosintácticas
240
+ if metrics['structure']['normalized_score'] < 0.6:
241
+ st.warning("""
242
+ #### 📝 Análisis Morfosintáctico Recomendado
243
+
244
+ **Tu nivel actual sugiere que sería beneficioso:**
245
+ 1. Realizar el análisis morfosintáctico de 3 párrafos diferentes
246
+ 2. Practicar la combinación de oraciones simples en compuestas
247
+ 3. Identificar y clasificar tipos de oraciones en textos académicos
248
+ 4. Ejercitar la variación sintáctica
249
+
250
+ *Hacer clic en "Comenzar ejercicios" para acceder al módulo morfosintáctico*
251
+ """)
252
+
253
+ # Recomendaciones semánticas
254
+ if metrics['vocabulary']['normalized_score'] < 0.7:
255
+ st.warning("""
256
+ #### 📚 Análisis Semántico Recomendado
257
+
258
+ **Para mejorar tu vocabulario y expresión:**
259
+ A. Realiza el análisis semántico de un texto académico
260
+ B. Identifica y agrupa campos semánticos relacionados
261
+ C. Practica la sustitución léxica en tus párrafos
262
+ D. Construye redes de conceptos sobre tu tema
263
+ E. Analiza las relaciones entre ideas principales
264
+
265
+ *Hacer clic en "Comenzar ejercicios" para acceder al módulo semántico*
266
+ """)
267
+
268
+ # Recomendaciones de cohesión
269
+ if metrics['cohesion']['normalized_score'] < 0.65:
270
+ st.warning("""
271
+ #### 🔄 Análisis del Discurso Recomendado
272
+
273
+ **Para mejorar la conexión entre ideas:**
274
+ 1. Realizar el análisis del discurso de un texto modelo
275
+ 2. Practicar el uso de diferentes conectores textuales
276
+ 3. Identificar cadenas de referencia en textos académicos
277
+ 4. Ejercitar la progresión temática en tus escritos
278
+
279
+ *Hacer clic en "Comenzar ejercicios" para acceder al módulo de análisis del discurso*
280
+ """)
281
+
282
+ # Botón de acción
283
+ st.markdown("---")
284
+ col1, col2, col3 = st.columns([1,2,1])
285
+ with col2:
286
+ st.button(
287
+ "🎯 Comenzar ejercicios recomendados",
288
+ type="primary",
289
+ use_container_width=True,
290
+ key="start_exercises"
291
+ )
modules/studentact/current_situation_interface-v3.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/studentact/current_situation_interface.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ from ..database.current_situation_mongo_db import store_current_situation_result
9
+
10
+ from .current_situation_analysis import (
11
+ analyze_text_dimensions,
12
+ analyze_clarity,
13
+ analyze_reference_clarity,
14
+ analyze_vocabulary_diversity,
15
+ analyze_cohesion,
16
+ analyze_structure,
17
+ get_dependency_depths,
18
+ normalize_score,
19
+ generate_sentence_graphs,
20
+ generate_word_connections,
21
+ generate_connection_paths,
22
+ create_vocabulary_network,
23
+ create_syntax_complexity_graph,
24
+ create_cohesion_heatmap,
25
+ )
26
+
27
+ # Configuración del estilo de matplotlib para el gráfico de radar
28
+ plt.rcParams['font.family'] = 'sans-serif'
29
+ plt.rcParams['axes.grid'] = True
30
+ plt.rcParams['axes.spines.top'] = False
31
+ plt.rcParams['axes.spines.right'] = False
32
+
33
+ logger = logging.getLogger(__name__)
34
+ ####################################
35
+
36
+ def display_current_situation_interface(lang_code, nlp_models, t):
37
+ """
38
+ Interfaz simplificada con gráfico de radar para visualizar métricas.
39
+ """
40
+ try:
41
+ # Inicializar estados si no existen
42
+ if 'text_input' not in st.session_state:
43
+ st.session_state.text_input = ""
44
+ if 'show_results' not in st.session_state:
45
+ st.session_state.show_results = False
46
+ if 'current_doc' not in st.session_state:
47
+ st.session_state.current_doc = None
48
+ if 'current_metrics' not in st.session_state:
49
+ st.session_state.current_metrics = None
50
+
51
+ st.markdown("## Análisis Inicial de Escritura")
52
+
53
+ # Container principal con dos columnas
54
+ with st.container():
55
+ input_col, results_col = st.columns([1,2])
56
+
57
+ with input_col:
58
+ #st.markdown("### Ingresa tu texto")
59
+
60
+ # Función para manejar cambios en el texto
61
+ def on_text_change():
62
+ st.session_state.text_input = st.session_state.text_area
63
+ st.session_state.show_results = False
64
+
65
+ # Text area con manejo de estado
66
+ text_input = st.text_area(
67
+ t.get('input_prompt', "Escribe o pega tu texto aquí:"),
68
+ height=400,
69
+ key="text_area",
70
+ value=st.session_state.text_input,
71
+ on_change=on_text_change,
72
+ help="Este texto será analizado para darte recomendaciones personalizadas"
73
+ )
74
+
75
+ if st.button(
76
+ t.get('analyze_button', "Analizar mi escritura"),
77
+ type="primary",
78
+ disabled=not text_input.strip(),
79
+ use_container_width=True,
80
+ ):
81
+ try:
82
+ with st.spinner(t.get('processing', "Analizando...")):
83
+ doc = nlp_models[lang_code](text_input)
84
+ metrics = analyze_text_dimensions(doc)
85
+
86
+ # Guardar en MongoDB
87
+ storage_success = store_current_situation_result(
88
+ username=st.session_state.username,
89
+ text=text_input,
90
+ metrics=metrics,
91
+ feedback=None
92
+ )
93
+
94
+ if not storage_success:
95
+ logger.warning("No se pudo guardar el análisis en la base de datos")
96
+
97
+ st.session_state.current_doc = doc
98
+ st.session_state.current_metrics = metrics
99
+ st.session_state.show_results = True
100
+ st.session_state.text_input = text_input
101
+
102
+ except Exception as e:
103
+ logger.error(f"Error en análisis: {str(e)}")
104
+ st.error(t.get('analysis_error', "Error al analizar el texto"))
105
+
106
+ # Mostrar resultados en la columna derecha
107
+ with results_col:
108
+ if st.session_state.show_results and st.session_state.current_metrics is not None:
109
+ display_radar_chart(st.session_state.current_metrics)
110
+
111
+ except Exception as e:
112
+ logger.error(f"Error en interfaz: {str(e)}")
113
+ st.error("Ocurrió un error. Por favor, intente de nuevo.")
114
+
115
+ def display_radar_chart(metrics):
116
+ """
117
+ Muestra un gráfico de radar con las métricas del usuario y el patrón ideal.
118
+ """
119
+ try:
120
+ # Container con proporción reducida
121
+ with st.container():
122
+ # Métricas en la parte superior
123
+ col1, col2, col3, col4 = st.columns(4)
124
+ with col1:
125
+ st.metric("Vocabulario", f"{metrics['vocabulary']['normalized_score']:.2f}", "1.00")
126
+ with col2:
127
+ st.metric("Estructura", f"{metrics['structure']['normalized_score']:.2f}", "1.00")
128
+ with col3:
129
+ st.metric("Cohesión", f"{metrics['cohesion']['normalized_score']:.2f}", "1.00")
130
+ with col4:
131
+ st.metric("Claridad", f"{metrics['clarity']['normalized_score']:.2f}", "1.00")
132
+
133
+ # Contenedor para el gráfico con ancho controlado
134
+ _, graph_col, _ = st.columns([1,2,1])
135
+
136
+ with graph_col:
137
+ # Preparar datos
138
+ categories = ['Vocabulario', 'Estructura', 'Cohesión', 'Claridad']
139
+ values_user = [
140
+ metrics['vocabulary']['normalized_score'],
141
+ metrics['structure']['normalized_score'],
142
+ metrics['cohesion']['normalized_score'],
143
+ metrics['clarity']['normalized_score']
144
+ ]
145
+ values_pattern = [1.0, 1.0, 1.0, 1.0] # Patrón ideal
146
+
147
+ # Crear figura más compacta
148
+ fig = plt.figure(figsize=(6, 6))
149
+ ax = fig.add_subplot(111, projection='polar')
150
+
151
+ # Número de variables
152
+ num_vars = len(categories)
153
+
154
+ # Calcular ángulos
155
+ angles = [n / float(num_vars) * 2 * np.pi for n in range(num_vars)]
156
+ angles += angles[:1]
157
+
158
+ # Extender valores para cerrar polígonos
159
+ values_user += values_user[:1]
160
+ values_pattern += values_pattern[:1]
161
+
162
+ # Configurar ejes y etiquetas
163
+ ax.set_xticks(angles[:-1])
164
+ ax.set_xticklabels(categories, fontsize=8)
165
+
166
+ # Círculos concéntricos y etiquetas
167
+ circle_ticks = np.arange(0, 1.1, 0.2) # Reducido a 5 niveles
168
+ ax.set_yticks(circle_ticks)
169
+ ax.set_yticklabels([f'{tick:.1f}' for tick in circle_ticks], fontsize=8)
170
+ ax.set_ylim(0, 1)
171
+
172
+ # Dibujar patrón ideal
173
+ ax.plot(angles, values_pattern, 'g--', linewidth=1, label='Patrón', alpha=0.5)
174
+ ax.fill(angles, values_pattern, 'g', alpha=0.1)
175
+
176
+ # Dibujar valores del usuario
177
+ ax.plot(angles, values_user, 'b-', linewidth=2, label='Tu escritura')
178
+ ax.fill(angles, values_user, 'b', alpha=0.2)
179
+
180
+ # Leyenda
181
+ ax.legend(loc='upper right', bbox_to_anchor=(0.1, 0.1), fontsize=8)
182
+
183
+ # Ajustes finales
184
+ plt.tight_layout()
185
+ st.pyplot(fig)
186
+ plt.close()
187
+
188
+ except Exception as e:
189
+ logger.error(f"Error generando gráfico de radar: {str(e)}")
190
+ st.error("Error al generar la visualización")
modules/studentact/current_situation_interface.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/studentact/current_situation_interface-vOK.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ from ..database.current_situation_mongo_db import store_current_situation_result
9
+
10
+ from .current_situation_analysis import (
11
+ analyze_text_dimensions,
12
+ analyze_clarity,
13
+ analyze_vocabulary_diversity,
14
+ analyze_cohesion,
15
+ analyze_structure,
16
+ get_dependency_depths,
17
+ normalize_score,
18
+ generate_sentence_graphs,
19
+ generate_word_connections,
20
+ generate_connection_paths,
21
+ create_vocabulary_network,
22
+ create_syntax_complexity_graph,
23
+ create_cohesion_heatmap,
24
+ )
25
+
26
+ # Configuración del estilo de matplotlib para el gráfico de radar
27
+ plt.rcParams['font.family'] = 'sans-serif'
28
+ plt.rcParams['axes.grid'] = True
29
+ plt.rcParams['axes.spines.top'] = False
30
+ plt.rcParams['axes.spines.right'] = False
31
+
32
+ logger = logging.getLogger(__name__)
33
+ ####################################
34
+
35
+ TEXT_TYPES = {
36
+ 'academic_article': {
37
+ 'name': 'Artículo Académico',
38
+ 'thresholds': {
39
+ 'vocabulary': {'min': 0.70, 'target': 0.85},
40
+ 'structure': {'min': 0.75, 'target': 0.90},
41
+ 'cohesion': {'min': 0.65, 'target': 0.80},
42
+ 'clarity': {'min': 0.70, 'target': 0.85}
43
+ }
44
+ },
45
+ 'student_essay': {
46
+ 'name': 'Trabajo Universitario',
47
+ 'thresholds': {
48
+ 'vocabulary': {'min': 0.60, 'target': 0.75},
49
+ 'structure': {'min': 0.65, 'target': 0.80},
50
+ 'cohesion': {'min': 0.55, 'target': 0.70},
51
+ 'clarity': {'min': 0.60, 'target': 0.75}
52
+ }
53
+ },
54
+ 'general_communication': {
55
+ 'name': 'Comunicación General',
56
+ 'thresholds': {
57
+ 'vocabulary': {'min': 0.50, 'target': 0.65},
58
+ 'structure': {'min': 0.55, 'target': 0.70},
59
+ 'cohesion': {'min': 0.45, 'target': 0.60},
60
+ 'clarity': {'min': 0.50, 'target': 0.65}
61
+ }
62
+ }
63
+ }
64
+ ####################################
65
+
66
+ def display_current_situation_interface(lang_code, nlp_models, t):
67
+ """
68
+ Interfaz simplificada con gráfico de radar para visualizar métricas.
69
+ """
70
+ # Inicializar estados si no existen
71
+ if 'text_input' not in st.session_state:
72
+ st.session_state.text_input = ""
73
+ if 'text_area' not in st.session_state: # Añadir inicialización de text_area
74
+ st.session_state.text_area = ""
75
+ if 'show_results' not in st.session_state:
76
+ st.session_state.show_results = False
77
+ if 'current_doc' not in st.session_state:
78
+ st.session_state.current_doc = None
79
+ if 'current_metrics' not in st.session_state:
80
+ st.session_state.current_metrics = None
81
+
82
+ try:
83
+ # Container principal con dos columnas
84
+ with st.container():
85
+ input_col, results_col = st.columns([1,2])
86
+
87
+ with input_col:
88
+ # Text area con manejo de estado
89
+ text_input = st.text_area(
90
+ t.get('input_prompt', "Escribe o pega tu texto aquí:"),
91
+ height=400,
92
+ key="text_area",
93
+ value=st.session_state.text_input,
94
+ help="Este texto será analizado para darte recomendaciones personalizadas"
95
+ )
96
+
97
+ # Función para manejar cambios de texto
98
+ if text_input != st.session_state.text_input:
99
+ st.session_state.text_input = text_input
100
+ st.session_state.show_results = False
101
+
102
+ if st.button(
103
+ t.get('analyze_button', "Analizar mi escritura"),
104
+ type="primary",
105
+ disabled=not text_input.strip(),
106
+ use_container_width=True,
107
+ ):
108
+ try:
109
+ with st.spinner(t.get('processing', "Analizando...")):
110
+ doc = nlp_models[lang_code](text_input)
111
+ metrics = analyze_text_dimensions(doc)
112
+
113
+ storage_success = store_current_situation_result(
114
+ username=st.session_state.username,
115
+ text=text_input,
116
+ metrics=metrics,
117
+ feedback=None
118
+ )
119
+
120
+ if not storage_success:
121
+ logger.warning("No se pudo guardar el análisis en la base de datos")
122
+
123
+ st.session_state.current_doc = doc
124
+ st.session_state.current_metrics = metrics
125
+ st.session_state.show_results = True
126
+
127
+ except Exception as e:
128
+ logger.error(f"Error en análisis: {str(e)}")
129
+ st.error(t.get('analysis_error', "Error al analizar el texto"))
130
+
131
+ # Mostrar resultados en la columna derecha
132
+ with results_col:
133
+ if st.session_state.show_results and st.session_state.current_metrics is not None:
134
+ # Primero los radio buttons para tipo de texto
135
+ st.markdown("### Tipo de texto")
136
+ text_type = st.radio(
137
+ "",
138
+ options=list(TEXT_TYPES.keys()),
139
+ format_func=lambda x: TEXT_TYPES[x]['name'],
140
+ horizontal=True,
141
+ key="text_type_radio",
142
+ help="Selecciona el tipo de texto para ajustar los criterios de evaluación"
143
+ )
144
+
145
+ st.session_state.current_text_type = text_type
146
+
147
+ # Luego mostrar los resultados
148
+ display_results(
149
+ metrics=st.session_state.current_metrics,
150
+ text_type=text_type
151
+ )
152
+
153
+ except Exception as e:
154
+ logger.error(f"Error en interfaz principal: {str(e)}")
155
+ st.error("Ocurrió un error al cargar la interfaz")
156
+
157
+ ###################################3333
158
+
159
+ def display_results(metrics, text_type=None):
160
+ """
161
+ Muestra los resultados del análisis: métricas verticalmente y gráfico radar.
162
+ """
163
+ try:
164
+ # Usar valor por defecto si no se especifica tipo
165
+ text_type = text_type or 'student_essay'
166
+
167
+ # Obtener umbrales según el tipo de texto
168
+ thresholds = TEXT_TYPES[text_type]['thresholds']
169
+
170
+ # Crear dos columnas para las métricas y el gráfico
171
+ metrics_col, graph_col = st.columns([1, 1.5])
172
+
173
+ # Columna de métricas
174
+ with metrics_col:
175
+ metrics_config = [
176
+ {
177
+ 'label': "Vocabulario",
178
+ 'key': 'vocabulary',
179
+ 'value': metrics['vocabulary']['normalized_score'],
180
+ 'help': "Riqueza y variedad del vocabulario",
181
+ 'thresholds': thresholds['vocabulary']
182
+ },
183
+ {
184
+ 'label': "Estructura",
185
+ 'key': 'structure',
186
+ 'value': metrics['structure']['normalized_score'],
187
+ 'help': "Organización y complejidad de oraciones",
188
+ 'thresholds': thresholds['structure']
189
+ },
190
+ {
191
+ 'label': "Cohesión",
192
+ 'key': 'cohesion',
193
+ 'value': metrics['cohesion']['normalized_score'],
194
+ 'help': "Conexión y fluidez entre ideas",
195
+ 'thresholds': thresholds['cohesion']
196
+ },
197
+ {
198
+ 'label': "Claridad",
199
+ 'key': 'clarity',
200
+ 'value': metrics['clarity']['normalized_score'],
201
+ 'help': "Facilidad de comprensión del texto",
202
+ 'thresholds': thresholds['clarity']
203
+ }
204
+ ]
205
+
206
+ # Mostrar métricas
207
+ for metric in metrics_config:
208
+ value = metric['value']
209
+ if value < metric['thresholds']['min']:
210
+ status = "⚠️ Por mejorar"
211
+ color = "inverse"
212
+ elif value < metric['thresholds']['target']:
213
+ status = "📈 Aceptable"
214
+ color = "off"
215
+ else:
216
+ status = "✅ Óptimo"
217
+ color = "normal"
218
+
219
+ st.metric(
220
+ metric['label'],
221
+ f"{value:.2f}",
222
+ f"{status} (Meta: {metric['thresholds']['target']:.2f})",
223
+ delta_color=color,
224
+ help=metric['help']
225
+ )
226
+ st.markdown("<div style='margin-bottom: 0.5rem;'></div>", unsafe_allow_html=True)
227
+
228
+ # Gráfico radar en la columna derecha
229
+ with graph_col:
230
+ display_radar_chart(metrics_config, thresholds)
231
+
232
+ except Exception as e:
233
+ logger.error(f"Error mostrando resultados: {str(e)}")
234
+ st.error("Error al mostrar los resultados")
235
+
236
+
237
+ ######################################
238
+ def display_radar_chart(metrics_config, thresholds):
239
+ """
240
+ Muestra el gráfico radar con los resultados.
241
+ """
242
+ try:
243
+ # Preparar datos para el gráfico
244
+ categories = [m['label'] for m in metrics_config]
245
+ values_user = [m['value'] for m in metrics_config]
246
+ min_values = [m['thresholds']['min'] for m in metrics_config]
247
+ target_values = [m['thresholds']['target'] for m in metrics_config]
248
+
249
+ # Crear y configurar gráfico
250
+ fig = plt.figure(figsize=(8, 8))
251
+ ax = fig.add_subplot(111, projection='polar')
252
+
253
+ # Configurar radar
254
+ angles = [n / float(len(categories)) * 2 * np.pi for n in range(len(categories))]
255
+ angles += angles[:1]
256
+ values_user += values_user[:1]
257
+ min_values += min_values[:1]
258
+ target_values += target_values[:1]
259
+
260
+ # Configurar ejes
261
+ ax.set_xticks(angles[:-1])
262
+ ax.set_xticklabels(categories, fontsize=10)
263
+ circle_ticks = np.arange(0, 1.1, 0.2)
264
+ ax.set_yticks(circle_ticks)
265
+ ax.set_yticklabels([f'{tick:.1f}' for tick in circle_ticks], fontsize=8)
266
+ ax.set_ylim(0, 1)
267
+
268
+ # Dibujar áreas de umbrales
269
+ ax.plot(angles, min_values, '#e74c3c', linestyle='--', linewidth=1, label='Mínimo', alpha=0.5)
270
+ ax.plot(angles, target_values, '#2ecc71', linestyle='--', linewidth=1, label='Meta', alpha=0.5)
271
+ ax.fill_between(angles, target_values, [1]*len(angles), color='#2ecc71', alpha=0.1)
272
+ ax.fill_between(angles, [0]*len(angles), min_values, color='#e74c3c', alpha=0.1)
273
+
274
+ # Dibujar valores del usuario
275
+ ax.plot(angles, values_user, '#3498db', linewidth=2, label='Tu escritura')
276
+ ax.fill(angles, values_user, '#3498db', alpha=0.2)
277
+
278
+ # Ajustar leyenda
279
+ ax.legend(
280
+ loc='upper right',
281
+ bbox_to_anchor=(1.3, 1.1), # Cambiado de (0.1, 0.1) a (1.3, 1.1)
282
+ fontsize=10,
283
+ frameon=True,
284
+ facecolor='white',
285
+ edgecolor='none',
286
+ shadow=True
287
+ )
288
+
289
+ plt.tight_layout()
290
+ st.pyplot(fig)
291
+ plt.close()
292
+
293
+ except Exception as e:
294
+ logger.error(f"Error mostrando gráfico radar: {str(e)}")
295
+ st.error("Error al mostrar el gráfico")
296
+ #######################################
modules/studentact/temp_current_situation_interface.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/studentact/current_situation_interface.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+ from .current_situation_analysis import (
7
+ analyze_text_dimensions,
8
+ create_vocabulary_network,
9
+ create_syntax_complexity_graph,
10
+ create_cohesion_heatmap
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def display_current_situation_interface(lang_code, nlp_models, t):
16
+ """
17
+ Interfaz modular para el análisis de la situación actual del estudiante.
18
+ Esta función maneja la presentación y la interacción con el usuario.
19
+
20
+ Args:
21
+ lang_code: Código del idioma actual
22
+ nlp_models: Diccionario de modelos de spaCy cargados
23
+ t: Diccionario de traducciones
24
+ """
25
+ st.markdown("## Mi Situación Actual de Escritura")
26
+
27
+ # Container principal para mejor organización visual
28
+ with st.container():
29
+ # Columnas para entrada y visualización
30
+ text_col, visual_col = st.columns([1,2])
31
+
32
+ with text_col:
33
+ # Área de entrada de texto
34
+ text_input = st.text_area(
35
+ t.get('current_situation_input', "Ingresa tu texto para analizar:"),
36
+ height=400,
37
+ key=generate_unique_key("current_situation", "input")
38
+ )
39
+
40
+ # Botón de análisis
41
+ if st.button(
42
+ t.get('analyze_button', "Explorar mi escritura"),
43
+ type="primary",
44
+ disabled=not text_input,
45
+ key=generate_unique_key("current_situation", "analyze")
46
+ ):
47
+ try:
48
+ with st.spinner(t.get('processing', "Analizando texto...")):
49
+ # 1. Procesar el texto
50
+ doc = nlp_models[lang_code](text_input)
51
+ metrics = analyze_text_dimensions(doc)
52
+
53
+ # 2. Mostrar visualizaciones en la columna derecha
54
+ with visual_col:
55
+ display_current_situation_visual(doc, metrics)
56
+
57
+ # 3. Obtener retroalimentación de Claude
58
+ feedback = get_claude_feedback(metrics, text_input)
59
+
60
+ # 4. Guardar los resultados
61
+ from ..database.current_situation_mongo_db import store_current_situation_result
62
+
63
+ if st.button(t.get('analyze_button', "Explorar mi escritura")):
64
+ with st.spinner(t.get('processing', "Analizando texto...")):
65
+ # Procesar y analizar
66
+ doc = nlp_models[lang_code](text_input)
67
+
68
+ # Obtener métricas con manejo de errores
69
+ try:
70
+ metrics = analyze_text_dimensions(doc)
71
+ except Exception as e:
72
+ logger.error(f"Error en análisis: {str(e)}")
73
+ st.error("Error en el análisis de dimensiones")
74
+ return
75
+
76
+ # Obtener feedback
77
+ try:
78
+ feedback = get_claude_feedback(metrics, text_input)
79
+ except Exception as e:
80
+ logger.error(f"Error obteniendo feedback: {str(e)}")
81
+ st.error("Error obteniendo retroalimentación")
82
+ return
83
+
84
+ # Guardar resultados con verificación
85
+ if store_current_situation_result(
86
+ st.session_state.username,
87
+ text_input,
88
+ metrics,
89
+ feedback
90
+ ):
91
+ st.success(t.get('save_success', "Análisis guardado"))
92
+
93
+ # Mostrar visualizaciones y recomendaciones
94
+ display_current_situation_visual(doc, metrics)
95
+ show_recommendations(feedback, t)
96
+ else:
97
+ st.error("Error al guardar el análisis")
98
+
99
+ except Exception as e:
100
+ logger.error(f"Error en interfaz: {str(e)}")
101
+ st.error("Error general en la interfaz")
102
+
103
+ ################################################################
104
+ def display_current_situation_visual(doc, metrics):
105
+ """Visualización mejorada de resultados con interpretaciones"""
106
+ try:
107
+ with st.container():
108
+ # Estilos CSS mejorados para los contenedores
109
+ st.markdown("""
110
+ <style>
111
+ .graph-container {
112
+ background-color: white;
113
+ border-radius: 10px;
114
+ padding: 20px;
115
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
116
+ margin: 15px 0;
117
+ }
118
+ .interpretation-box {
119
+ background-color: #f8f9fa;
120
+ border-left: 4px solid #0d6efd;
121
+ padding: 15px;
122
+ margin: 10px 0;
123
+ }
124
+ .metric-indicator {
125
+ font-size: 1.2em;
126
+ font-weight: 500;
127
+ color: #1f2937;
128
+ }
129
+ </style>
130
+ """, unsafe_allow_html=True)
131
+
132
+ # 1. Riqueza de Vocabulario
133
+ with st.expander("📚 Riqueza de Vocabulario", expanded=True):
134
+ st.markdown('<div class="graph-container">', unsafe_allow_html=True)
135
+ vocabulary_graph = create_vocabulary_network(doc)
136
+ if vocabulary_graph:
137
+ # Mostrar gráfico
138
+ st.pyplot(vocabulary_graph)
139
+ plt.close(vocabulary_graph)
140
+
141
+ # Interpretación
142
+ st.markdown('<div class="interpretation-box">', unsafe_allow_html=True)
143
+ st.markdown("**¿Qué significa este gráfico?**")
144
+ st.markdown("""
145
+ - 🔵 Los nodos azules representan palabras clave en tu texto
146
+ - 📏 El tamaño de cada nodo indica su frecuencia de uso
147
+ - 🔗 Las líneas conectan palabras que aparecen juntas frecuentemente
148
+ - 🎨 Los colores más intensos indican palabras más centrales
149
+ """)
150
+ st.markdown("</div>", unsafe_allow_html=True)
151
+ st.markdown("</div>", unsafe_allow_html=True)
152
+
153
+ # 2. Estructura de Oraciones
154
+ with st.expander("🏗️ Complejidad Estructural", expanded=True):
155
+ st.markdown('<div class="graph-container">', unsafe_allow_html=True)
156
+ syntax_graph = create_syntax_complexity_graph(doc)
157
+ if syntax_graph:
158
+ st.pyplot(syntax_graph)
159
+ plt.close(syntax_graph)
160
+
161
+ st.markdown('<div class="interpretation-box">', unsafe_allow_html=True)
162
+ st.markdown("**Análisis de la estructura:**")
163
+ st.markdown("""
164
+ - 📊 Las barras muestran la complejidad de cada oración
165
+ - 📈 Mayor altura indica estructuras más elaboradas
166
+ - 🎯 La línea punteada indica el nivel óptimo de complejidad
167
+ - 🔄 Variación en las alturas sugiere dinamismo en la escritura
168
+ """)
169
+ st.markdown("</div>", unsafe_allow_html=True)
170
+ st.markdown("</div>", unsafe_allow_html=True)
171
+
172
+ # 3. Cohesión Textual
173
+ with st.expander("🔄 Cohesión del Texto", expanded=True):
174
+ st.markdown('<div class="graph-container">', unsafe_allow_html=True)
175
+ cohesion_map = create_cohesion_heatmap(doc)
176
+ if cohesion_map:
177
+ st.pyplot(cohesion_map)
178
+ plt.close(cohesion_map)
179
+
180
+ st.markdown('<div class="interpretation-box">', unsafe_allow_html=True)
181
+ st.markdown("**¿Cómo leer el mapa de calor?**")
182
+ st.markdown("""
183
+ - 🌈 Colores más intensos indican mayor conexión entre oraciones
184
+ - 📝 La diagonal muestra la coherencia interna de cada oración
185
+ - 🔗 Las zonas claras sugieren oportunidades de mejorar conexiones
186
+ - 🎯 Un buen texto muestra patrones de color consistentes
187
+ """)
188
+ st.markdown("</div>", unsafe_allow_html=True)
189
+ st.markdown("</div>", unsafe_allow_html=True)
190
+
191
+ # 4. Métricas Generales
192
+ with st.expander("📊 Resumen de Métricas", expanded=True):
193
+ col1, col2, col3 = st.columns(3)
194
+
195
+ with col1:
196
+ st.metric(
197
+ "Diversidad Léxica",
198
+ f"{metrics['vocabulary_richness']:.2f}/1.0",
199
+ help="Mide la variedad de palabras diferentes utilizadas"
200
+ )
201
+
202
+ with col2:
203
+ st.metric(
204
+ "Complejidad Estructural",
205
+ f"{metrics['structural_complexity']:.2f}/1.0",
206
+ help="Indica qué tan elaboradas son las estructuras de las oraciones"
207
+ )
208
+
209
+ with col3:
210
+ st.metric(
211
+ "Cohesión Textual",
212
+ f"{metrics['cohesion_score']:.2f}/1.0",
213
+ help="Evalúa qué tan bien conectadas están las ideas entre sí"
214
+ )
215
+
216
+ except Exception as e:
217
+ logger.error(f"Error en visualización: {str(e)}")
218
+ st.error("Error al generar las visualizaciones")
219
+
220
+ ################################################################
221
+ def show_recommendations(feedback, t):
222
+ """
223
+ Muestra las recomendaciones y ejercicios personalizados para el estudiante,
224
+ permitiendo el seguimiento de su progreso.
225
+
226
+ Args:
227
+ feedback: Diccionario con retroalimentación y ejercicios recomendados
228
+ t: Diccionario de traducciones
229
+ """
230
+ st.markdown("### " + t.get('recommendations_title', "Recomendaciones para mejorar"))
231
+
232
+ for area, exercises in feedback['recommendations'].items():
233
+ with st.expander(f"💡 {area}"):
234
+ try:
235
+ # Descripción del área de mejora
236
+ st.markdown(exercises['description'])
237
+
238
+ # Obtener el historial de ejercicios del estudiante
239
+ from ..database.current_situation_mongo_db import get_student_exercises_history
240
+ exercises_history = get_student_exercises_history(st.session_state.username)
241
+
242
+ # Separar ejercicios en completados y pendientes
243
+ completed = exercises_history.get(area, [])
244
+
245
+ # Mostrar estado actual
246
+ progress_col1, progress_col2 = st.columns([3,1])
247
+ with progress_col1:
248
+ st.markdown("**Ejercicio sugerido:**")
249
+ st.markdown(exercises['activity'])
250
+
251
+ with progress_col2:
252
+ # Verificar si el ejercicio ya está completado
253
+ exercise_key = f"{area}_{exercises['activity']}"
254
+ is_completed = exercise_key in completed
255
+
256
+ if is_completed:
257
+ st.success("✅ Completado")
258
+ else:
259
+ # Botón para marcar ejercicio como completado
260
+ if st.button(
261
+ t.get('mark_complete', "Marcar como completado"),
262
+ key=generate_unique_key("exercise", area),
263
+ type="primary"
264
+ ):
265
+ try:
266
+ from ..database.current_situation_mongo_db import update_exercise_status
267
+
268
+ # Actualizar estado del ejercicio
269
+ success = update_exercise_status(
270
+ username=st.session_state.username,
271
+ area=area,
272
+ exercise=exercises['activity'],
273
+ completed=True
274
+ )
275
+
276
+ if success:
277
+ st.success(t.get(
278
+ 'exercise_completed',
279
+ "¡Ejercicio marcado como completado!"
280
+ ))
281
+ st.rerun()
282
+ else:
283
+ st.error(t.get(
284
+ 'exercise_error',
285
+ "Error al actualizar el estado del ejercicio"
286
+ ))
287
+ except Exception as e:
288
+ logger.error(f"Error actualizando estado del ejercicio: {str(e)}")
289
+ st.error(t.get('update_error', "Error al actualizar el ejercicio"))
290
+
291
+ # Mostrar recursos adicionales si existen
292
+ if 'resources' in exercises:
293
+ st.markdown("**Recursos adicionales:**")
294
+ for resource in exercises['resources']:
295
+ st.markdown(f"- {resource}")
296
+
297
+ # Mostrar fecha de finalización si está completado
298
+ if is_completed:
299
+ completion_date = exercises_history[exercise_key].get('completion_date')
300
+ if completion_date:
301
+ st.caption(
302
+ t.get('completed_on', "Completado el") +
303
+ f": {completion_date.strftime('%d/%m/%Y %H:%M')}"
304
+ )
305
+
306
+ except Exception as e:
307
+ logger.error(f"Error mostrando recomendaciones para {area}: {str(e)}")
308
+ st.error(t.get(
309
+ 'recommendations_error',
310
+ f"Error al mostrar las recomendaciones para {area}"
311
+ ))
modules/ui/ui.py CHANGED
@@ -17,14 +17,18 @@ from session_state import initialize_session_state, logout
17
 
18
  from translations import get_translations
19
 
20
- from ..studentact.student_activities_v2 import display_student_activities
21
-
22
  from ..auth.auth import authenticate_user, authenticate_student, authenticate_admin
23
 
24
  from ..admin.admin_ui import admin_page
25
 
26
  from ..chatbot import display_sidebar_chat
27
 
 
 
 
 
 
 
28
  ##Importaciones desde la configuración de bases datos #######
29
 
30
  from ..database.sql_db import (
@@ -403,6 +407,7 @@ def user_page(lang_code, t):
403
  # Inicializar estados para todos los tabs
404
  if 'tab_states' not in st.session_state:
405
  st.session_state.tab_states = {
 
406
  'morpho_active': False,
407
  'semantic_live_active': False,
408
  'semantic_active': False,
@@ -414,6 +419,7 @@ def user_page(lang_code, t):
414
 
415
  # Sistema de tabs
416
  tab_names = [
 
417
  t.get('morpho_tab', 'Análisis Morfosintáctico'),
418
  t.get('semantic_live_tab', 'Análisis Semántico Vivo'),
419
  t.get('semantic_tab', 'Análisis Semántico'),
@@ -439,7 +445,15 @@ def user_page(lang_code, t):
439
  if can_switch:
440
  st.session_state.selected_tab = index
441
 
442
- if index == 0: # Morfosintáctico
 
 
 
 
 
 
 
 
443
  st.session_state.tab_states['morpho_active'] = True
444
  display_morphosyntax_interface(
445
  st.session_state.lang_code,
@@ -447,7 +461,8 @@ def user_page(lang_code, t):
447
  t.get('TRANSLATIONS', {})
448
  )
449
 
450
- elif index == 1: # Semántico Vivo
 
451
  st.session_state.tab_states['semantic_live_active'] = True
452
  display_semantic_live_interface(
453
  st.session_state.lang_code,
@@ -455,7 +470,7 @@ def user_page(lang_code, t):
455
  t.get('TRANSLATIONS', {})
456
  )
457
 
458
- elif index == 2: # Semántico
459
  st.session_state.tab_states['semantic_active'] = True
460
  display_semantic_interface(
461
  st.session_state.lang_code,
@@ -463,7 +478,7 @@ def user_page(lang_code, t):
463
  t.get('TRANSLATIONS', {})
464
  )
465
 
466
- elif index == 3: # Discurso Vivo
467
  st.session_state.tab_states['discourse_live_active'] = True
468
  display_discourse_live_interface(
469
  st.session_state.lang_code,
@@ -472,7 +487,7 @@ def user_page(lang_code, t):
472
  )
473
 
474
 
475
- elif index == 4: # Discurso
476
  st.session_state.tab_states['discourse_active'] = True
477
  display_discourse_interface(
478
  st.session_state.lang_code,
@@ -480,7 +495,7 @@ def user_page(lang_code, t):
480
  t.get('TRANSLATIONS', {})
481
  )
482
 
483
- elif index == 5: # Actividades
484
  st.session_state.tab_states['activities_active'] = True
485
  display_student_activities(
486
  username=st.session_state.username,
@@ -488,7 +503,7 @@ def user_page(lang_code, t):
488
  t=t.get('ACTIVITIES_TRANSLATIONS', {})
489
  )
490
 
491
- elif index == 6: # Feedback
492
  st.session_state.tab_states['feedback_active'] = True
493
  display_feedback_form(
494
  st.session_state.lang_code,
@@ -507,26 +522,28 @@ def user_page(lang_code, t):
507
  def get_tab_index(state_key):
508
  """Obtiene el índice del tab basado en la clave de estado"""
509
  index_map = {
510
- 'morpho_active': 0,
511
- 'semantic_live_active': 1,
512
- 'semantic_active': 2,
513
- 'discourse_live_active': 3,
514
- 'discourse_active': 4,
515
- 'activities_active': 5,
516
- 'feedback_active': 6
 
517
  }
518
  return index_map.get(state_key, -1)
519
 
520
  def get_state_key_for_index(index):
521
  """Obtiene la clave de estado basada en el índice del tab"""
522
  state_map = {
523
- 0: 'morpho_active',
524
- 1: 'semantic_live_active',
525
- 2: 'semantic_active',
526
- 3: 'discourse_live_active',
527
- 3: 'discourse_active',
528
- 4: 'activities_active',
529
- 5: 'feedback_active'
 
530
  }
531
  return state_map.get(index)
532
 
 
17
 
18
  from translations import get_translations
19
 
 
 
20
  from ..auth.auth import authenticate_user, authenticate_student, authenticate_admin
21
 
22
  from ..admin.admin_ui import admin_page
23
 
24
  from ..chatbot import display_sidebar_chat
25
 
26
+ # Students activities
27
+ from ..studentact.student_activities_v2 import display_student_activities
28
+ from ..studentact.current_situation_interface import display_current_situation_interface
29
+ from ..studentact.current_situation_analysis import analyze_text_dimensions
30
+
31
+
32
  ##Importaciones desde la configuración de bases datos #######
33
 
34
  from ..database.sql_db import (
 
407
  # Inicializar estados para todos los tabs
408
  if 'tab_states' not in st.session_state:
409
  st.session_state.tab_states = {
410
+ 'current_situation_active': False,
411
  'morpho_active': False,
412
  'semantic_live_active': False,
413
  'semantic_active': False,
 
419
 
420
  # Sistema de tabs
421
  tab_names = [
422
+ t.get('current_situation_tab', "Mi Situación Actual"),
423
  t.get('morpho_tab', 'Análisis Morfosintáctico'),
424
  t.get('semantic_live_tab', 'Análisis Semántico Vivo'),
425
  t.get('semantic_tab', 'Análisis Semántico'),
 
445
  if can_switch:
446
  st.session_state.selected_tab = index
447
 
448
+ if index == 0: # Situación actual
449
+ st.session_state.tab_states['current_situation_active'] = True
450
+ display_current_situation_interface(
451
+ st.session_state.lang_code,
452
+ st.session_state.nlp_models,
453
+ t.get('TRANSLATIONS', {})
454
+ )
455
+
456
+ elif index == 1: # Morfosintáctico
457
  st.session_state.tab_states['morpho_active'] = True
458
  display_morphosyntax_interface(
459
  st.session_state.lang_code,
 
461
  t.get('TRANSLATIONS', {})
462
  )
463
 
464
+
465
+ elif index == 2: # Semántico Vivo
466
  st.session_state.tab_states['semantic_live_active'] = True
467
  display_semantic_live_interface(
468
  st.session_state.lang_code,
 
470
  t.get('TRANSLATIONS', {})
471
  )
472
 
473
+ elif index == 3: # Semántico
474
  st.session_state.tab_states['semantic_active'] = True
475
  display_semantic_interface(
476
  st.session_state.lang_code,
 
478
  t.get('TRANSLATIONS', {})
479
  )
480
 
481
+ elif index == 4: # Discurso Vivo
482
  st.session_state.tab_states['discourse_live_active'] = True
483
  display_discourse_live_interface(
484
  st.session_state.lang_code,
 
487
  )
488
 
489
 
490
+ elif index == 5: # Discurso
491
  st.session_state.tab_states['discourse_active'] = True
492
  display_discourse_interface(
493
  st.session_state.lang_code,
 
495
  t.get('TRANSLATIONS', {})
496
  )
497
 
498
+ elif index == 6: # Actividades
499
  st.session_state.tab_states['activities_active'] = True
500
  display_student_activities(
501
  username=st.session_state.username,
 
503
  t=t.get('ACTIVITIES_TRANSLATIONS', {})
504
  )
505
 
506
+ elif index == 7: # Feedback
507
  st.session_state.tab_states['feedback_active'] = True
508
  display_feedback_form(
509
  st.session_state.lang_code,
 
522
  def get_tab_index(state_key):
523
  """Obtiene el índice del tab basado en la clave de estado"""
524
  index_map = {
525
+ 'current_situation_active': 0,
526
+ 'morpho_active': 1,
527
+ 'semantic_live_active': 2,
528
+ 'semantic_active': 3,
529
+ 'discourse_live_active': 4,
530
+ 'discourse_active': 5,
531
+ 'activities_active': 6,
532
+ 'feedback_active': 7
533
  }
534
  return index_map.get(state_key, -1)
535
 
536
  def get_state_key_for_index(index):
537
  """Obtiene la clave de estado basada en el índice del tab"""
538
  state_map = {
539
+ 0: 'current_situation_active',
540
+ 1: 'morpho_active',
541
+ 2: 'semantic_live_active',
542
+ 3: 'semantic_active',
543
+ 4: 'discourse_live_active',
544
+ 5: 'discourse_active',
545
+ 6: 'activities_active',
546
+ 7: 'feedback_active'
547
  }
548
  return state_map.get(index)
549
 
pre-requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ #https://huggingface.co/spacy/es_core_news_lg/resolve/main/es_core_news_lg-any-py3-none-any.whl
2
+ #https://huggingface.co/spacy/en_core_web_lg/resolve/main/en_core_web_lg-any-py3-none-any.whl
3
+ #https://huggingface.co/spacy/fr_core_news_lg/resolve/main/fr_core_news_lg-any-py3-none-any.whl
requirements.txt CHANGED
@@ -8,12 +8,17 @@ cairosvg
8
  python-dotenv
9
  drawSvg
10
  docx2txt
11
- https://huggingface.co/spacy/es_core_news_lg/resolve/main/es_core_news_lg-any-py3-none-any.whl
12
- https://huggingface.co/spacy/en_core_web_lg/resolve/main/en_core_web_lg-any-py3-none-any.whl
13
- https://huggingface.co/spacy/fr_core_news_lg/resolve/main/fr_core_news_lg-any-py3-none-any.whl
14
- #es-core-news-lg @ https://github.com/explosion/spacy-models/releases/download/es_core_news_lg-3.5.0/es_core_news_lg-3.5.0-py3-none-any.whl
15
- #en-core-web-lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.5.0/en_core_web_lg-3.5.0-py3-none-any.whl
16
- #fr-core-news-lg @ https://github.com/explosion/spacy-models/releases/download/fr_core_news_lg-3.5.0/fr_core_news_lg-3.5.0-py3-none-any.whl
 
 
 
 
 
17
  numpy
18
  networkx
19
  matplotlib
@@ -31,7 +36,8 @@ PyPDF2
31
  rlPyCairo
32
  requests
33
  reportlab
34
- spacy
 
35
  spacy-streamlit
36
  seaborn
37
  squarify
 
8
  python-dotenv
9
  drawSvg
10
  docx2txt
11
+
12
+ # Modelos de spaCy desde Hugging Face
13
+ # https://huggingface.co/spacy/es_core_news_lg/resolve/main/es_core_news_lg-any-py3-none-any.whl
14
+ # https://huggingface.co/spacy/en_core_web_lg/resolve/main/en_core_web_lg-any-py3-none-any.whl
15
+ # https://huggingface.co/spacy/fr_core_news_lg/resolve/main/fr_core_news_lg-any-py3-none-any.whl
16
+
17
+ # Enlaces alternativos desde GitHub (comentados)
18
+ es-core-news-lg @ https://github.com/explosion/spacy-models/releases/download/es_core_news_lg-3.7.0/es_core_news_lg-3.7.0-py3-none-any.whl
19
+ en-core-web-lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.7.1/en_core_web_lg-3.7.1-py3-none-any.whl
20
+ fr-core-news-lg @ https://github.com/explosion/spacy-models/releases/download/fr_core_news_lg-3.7.0/fr_core_news_lg-3.7.0-py3-none-any.whl
21
+
22
  numpy
23
  networkx
24
  matplotlib
 
36
  rlPyCairo
37
  requests
38
  reportlab
39
+ spacy>=3.7.0,<3.8.0
40
+ #spacy
41
  spacy-streamlit
42
  seaborn
43
  squarify