v4 / modules /studentact /current_situation_interface.py
AIdeaText's picture
Update modules/studentact/current_situation_interface.py
ace33ce verified
raw
history blame
21.6 kB
# modules/studentact/current_situation_interface.py
import streamlit as st
import logging
from ..utils.widget_utils import generate_unique_key
import matplotlib.pyplot as plt
import numpy as np
from ..database.current_situation_mongo_db import store_current_situation_result
from ..database.writing_progress_mongo_db import (
store_writing_baseline,
store_writing_progress,
get_writing_baseline,
get_writing_progress,
get_latest_writing_metrics
)
from .current_situation_analysis import (
analyze_text_dimensions,
analyze_clarity,
analyze_vocabulary_diversity,
analyze_cohesion,
analyze_structure,
get_dependency_depths,
normalize_score,
generate_sentence_graphs,
generate_word_connections,
generate_connection_paths,
create_vocabulary_network,
create_syntax_complexity_graph,
create_cohesion_heatmap
)
# Configuración del estilo de matplotlib para el gráfico de radar
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['axes.grid'] = True
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
logger = logging.getLogger(__name__)
####################################
TEXT_TYPES = {
'academic_article': {
'name': 'Artículo Académico',
'thresholds': {
'vocabulary': {'min': 0.70, 'target': 0.85},
'structure': {'min': 0.75, 'target': 0.90},
'cohesion': {'min': 0.65, 'target': 0.80},
'clarity': {'min': 0.70, 'target': 0.85}
}
},
'student_essay': {
'name': 'Trabajo Universitario',
'thresholds': {
'vocabulary': {'min': 0.60, 'target': 0.75},
'structure': {'min': 0.65, 'target': 0.80},
'cohesion': {'min': 0.55, 'target': 0.70},
'clarity': {'min': 0.60, 'target': 0.75}
}
},
'general_communication': {
'name': 'Comunicación General',
'thresholds': {
'vocabulary': {'min': 0.50, 'target': 0.65},
'structure': {'min': 0.55, 'target': 0.70},
'cohesion': {'min': 0.45, 'target': 0.60},
'clarity': {'min': 0.50, 'target': 0.65}
}
}
}
####################################
ANALYSIS_DIMENSION_MAPPING = {
'morphosyntactic': {
'primary': ['vocabulary', 'clarity'],
'secondary': ['structure'],
'tools': ['arc_diagrams', 'word_repetition']
},
'semantic': {
'primary': ['cohesion', 'structure'],
'secondary': ['vocabulary'],
'tools': ['concept_graphs', 'semantic_networks']
},
'discourse': {
'primary': ['cohesion', 'structure'],
'secondary': ['clarity'],
'tools': ['comparative_analysis']
}
}
####################################
def display_current_situation_interface(lang_code, nlp_models, t):
"""
Interfaz con línea base y progreso lado a lado.
"""
# Inicializar estados
if 'baseline_text' not in st.session_state:
# Intentar recuperar línea base guardada
baseline = get_writing_baseline(st.session_state.username)
st.session_state.baseline_text = baseline['text'] if baseline else ""
st.session_state.baseline_metrics = baseline['metrics'] if baseline else None
if 'iteration_count' not in st.session_state:
st.session_state.iteration_count = 0
try:
st.title("Análisis de Escritura")
# Crear dos columnas principales
col1, col2 = st.columns(2)
# Columna izquierda: Línea Base
with col1:
st.markdown("### Línea Base")
baseline_text = st.text_area(
"Texto base",
value=st.session_state.baseline_text,
height=200,
key="baseline_area",
help="Este texto servirá como punto de referencia"
)
if st.button("Establecer Línea Base", type="primary"):
with st.spinner("Analizando línea base..."):
doc = nlp_models[lang_code](baseline_text)
metrics = analyze_text_dimensions(doc)
success = store_writing_baseline(
username=st.session_state.username,
metrics=metrics,
text=baseline_text
)
if success:
st.session_state.baseline_text = baseline_text
st.session_state.baseline_metrics = metrics
st.success("Línea base establecida")
# Mostrar métricas de línea base
display_metrics_column(metrics, "Línea Base")
# Columna derecha: Progreso
with col2:
st.markdown(f"### Iteración #{st.session_state.iteration_count + 1}")
current_text = st.text_area(
"Texto actual",
height=200,
key="current_area",
help="Escribe la nueva versión de tu texto"
)
if st.button("Analizar Progreso", type="primary"):
if not st.session_state.baseline_metrics:
st.error("Primero debes establecer una línea base")
return
with st.spinner("Analizando progreso..."):
doc = nlp_models[lang_code](current_text)
current_metrics = analyze_text_dimensions(doc)
# Incrementar contador de iteraciones
st.session_state.iteration_count += 1
# Guardar progreso
store_writing_progress(
username=st.session_state.username,
metrics=current_metrics,
text=current_text
)
# Mostrar métricas actuales
display_metrics_column(current_metrics, f"Iteración #{st.session_state.iteration_count}")
# Expander con gráfico radar
with st.expander("Ver Comparación Visual", expanded=False):
if st.session_state.baseline_metrics and 'current_metrics' in locals():
baseline_config = prepare_metrics_config(st.session_state.baseline_metrics)
current_config = prepare_metrics_config(current_metrics)
display_radar_chart(
metrics_config=current_config,
thresholds=TEXT_TYPES['student_essay']['thresholds'],
baseline_metrics=st.session_state.baseline_metrics
)
except Exception as e:
logger.error(f"Error en interfaz: {str(e)}")
st.error("Error al cargar la interfaz")
###################################
def display_metrics_column(metrics, title):
"""Muestra columna de métricas con formato consistente"""
st.markdown(f"#### Métricas {title}")
for dimension in ['vocabulary', 'structure', 'cohesion', 'clarity']:
value = metrics[dimension]['normalized_score']
if value < 0.6:
status = "⚠️ Por mejorar"
color = "inverse"
elif value < 0.8:
status = "📈 Aceptable"
color = "off"
else:
status = "✅ Óptimo"
color = "normal"
st.metric(
dimension.title(),
f"{value:.2f}",
status,
delta_color=color
)
###################################
def display_baseline_interface(lang_code, nlp_models, t):
"""Interfaz para establecer línea base"""
try:
st.markdown("### Establecer Línea Base")
text_input = st.text_area(
"Texto para línea base",
height=300,
help="Este texto servirá como punto de referencia para medir tu progreso"
)
if st.button("Establecer como línea base", type="primary"):
with st.spinner("Analizando texto base..."):
# Analizar el texto
doc = nlp_models[lang_code](text_input)
metrics = analyze_text_dimensions(doc)
# Guardar como línea base
success = store_writing_baseline(
username=st.session_state.username,
metrics=metrics,
text=text_input
)
if success:
st.success("Línea base establecida con éxito")
# Mostrar el gráfico radar inicial
metrics_config = prepare_metrics_config(metrics)
display_radar_chart(metrics_config, TEXT_TYPES['student_essay']['thresholds'])
else:
st.error("Error al guardar la línea base")
except Exception as e:
logger.error(f"Error en interfaz de línea base: {str(e)}")
st.error("Error al establecer línea base")
###################################
def display_comparison_interface(lang_code, nlp_models, t):
"""Interfaz para comparar progreso"""
try:
# Obtener línea base
baseline = get_writing_baseline(st.session_state.username)
if not baseline:
st.warning("Primero debes establecer una línea base")
return
# Crear dos columnas
col1, col2 = st.columns(2)
with col1:
st.markdown("### Línea Base")
st.text_area(
"Texto original",
value=baseline['text'],
disabled=True,
height=200
)
with col2:
st.markdown("### Nuevo Texto")
current_text = st.text_area(
"Ingresa el nuevo texto a comparar",
height=200
)
if st.button("Analizar progreso", type="primary"):
with st.spinner("Analizando progreso..."):
# Analizar texto actual
doc = nlp_models[lang_code](current_text)
current_metrics = analyze_text_dimensions(doc)
# Mostrar comparación
display_comparison_results(
baseline_metrics=baseline['metrics'],
current_metrics=current_metrics
)
# Opción para guardar progreso
if st.button("Guardar este progreso"):
success = store_writing_progress(
username=st.session_state.username,
metrics=current_metrics,
text=current_text
)
if success:
st.success("Progreso guardado exitosamente")
else:
st.error("Error al guardar el progreso")
except Exception as e:
logger.error(f"Error en interfaz de comparación: {str(e)}")
st.error("Error al mostrar comparación")
###################################
def display_comparison_results(baseline_metrics, current_metrics):
"""Muestra comparación entre línea base y métricas actuales"""
# Crear columnas para métricas y gráfico
metrics_col, graph_col = st.columns([1, 1.5])
with metrics_col:
for dimension in ['vocabulary', 'structure', 'cohesion', 'clarity']:
baseline = baseline_metrics[dimension]['normalized_score']
current = current_metrics[dimension]['normalized_score']
delta = current - baseline
st.metric(
dimension.title(),
f"{current:.2f}",
f"{delta:+.2f}",
delta_color="normal" if delta >= 0 else "inverse"
)
# Sugerir herramientas de mejora
if delta < 0:
suggest_improvement_tools(dimension)
with graph_col:
display_radar_chart_comparison(
baseline_metrics,
current_metrics
)
###################################
def suggest_improvement_tools(dimension):
"""Sugiere herramientas basadas en la dimensión"""
suggestions = []
for analysis, mapping in ANALYSIS_DIMENSION_MAPPING.items():
if dimension in mapping['primary']:
suggestions.extend(mapping['tools'])
st.info(f"Herramientas sugeridas para mejorar {dimension}:")
for tool in suggestions:
st.write(f"- {tool}")
###################################
def prepare_metrics_config(metrics, text_type='student_essay'):
"""
Prepara la configuración de métricas en el mismo formato que display_results.
Args:
metrics: Diccionario con las métricas analizadas
text_type: Tipo de texto para los umbrales
Returns:
list: Lista de configuraciones de métricas
"""
# Obtener umbrales según el tipo de texto
thresholds = TEXT_TYPES[text_type]['thresholds']
# Usar la misma estructura que en display_results
return [
{
'label': "Vocabulario",
'key': 'vocabulary',
'value': metrics['vocabulary']['normalized_score'],
'help': "Riqueza y variedad del vocabulario",
'thresholds': thresholds['vocabulary']
},
{
'label': "Estructura",
'key': 'structure',
'value': metrics['structure']['normalized_score'],
'help': "Organización y complejidad de oraciones",
'thresholds': thresholds['structure']
},
{
'label': "Cohesión",
'key': 'cohesion',
'value': metrics['cohesion']['normalized_score'],
'help': "Conexión y fluidez entre ideas",
'thresholds': thresholds['cohesion']
},
{
'label': "Claridad",
'key': 'clarity',
'value': metrics['clarity']['normalized_score'],
'help': "Facilidad de comprensión del texto",
'thresholds': thresholds['clarity']
}
]
###################################
def display_results(metrics, text_type=None):
"""
Muestra los resultados del análisis: métricas verticalmente y gráfico radar.
"""
try:
# Usar valor por defecto si no se especifica tipo
text_type = text_type or 'student_essay'
# Obtener umbrales según el tipo de texto
thresholds = TEXT_TYPES[text_type]['thresholds']
# Crear dos columnas para las métricas y el gráfico
metrics_col, graph_col = st.columns([1, 1.5])
# Columna de métricas
with metrics_col:
metrics_config = [
{
'label': "Vocabulario",
'key': 'vocabulary',
'value': metrics['vocabulary']['normalized_score'],
'help': "Riqueza y variedad del vocabulario",
'thresholds': thresholds['vocabulary']
},
{
'label': "Estructura",
'key': 'structure',
'value': metrics['structure']['normalized_score'],
'help': "Organización y complejidad de oraciones",
'thresholds': thresholds['structure']
},
{
'label': "Cohesión",
'key': 'cohesion',
'value': metrics['cohesion']['normalized_score'],
'help': "Conexión y fluidez entre ideas",
'thresholds': thresholds['cohesion']
},
{
'label': "Claridad",
'key': 'clarity',
'value': metrics['clarity']['normalized_score'],
'help': "Facilidad de comprensión del texto",
'thresholds': thresholds['clarity']
}
]
# Mostrar métricas
for metric in metrics_config:
value = metric['value']
if value < metric['thresholds']['min']:
status = "⚠️ Por mejorar"
color = "inverse"
elif value < metric['thresholds']['target']:
status = "📈 Aceptable"
color = "off"
else:
status = "✅ Óptimo"
color = "normal"
st.metric(
metric['label'],
f"{value:.2f}",
f"{status} (Meta: {metric['thresholds']['target']:.2f})",
delta_color=color,
help=metric['help']
)
st.markdown("<div style='margin-bottom: 0.5rem;'></div>", unsafe_allow_html=True)
# Gráfico radar en la columna derecha
with graph_col:
display_radar_chart(metrics_config, thresholds)
except Exception as e:
logger.error(f"Error mostrando resultados: {str(e)}")
st.error("Error al mostrar los resultados")
######################################
def display_radar_chart(metrics_config, thresholds, baseline_metrics=None):
"""
Muestra el gráfico radar con los resultados.
Args:
metrics_config: Configuración actual de métricas
thresholds: Umbrales para las métricas
baseline_metrics: Métricas de línea base (opcional)
"""
try:
# Preparar datos para el gráfico
categories = [m['label'] for m in metrics_config]
values_current = [m['value'] for m in metrics_config]
min_values = [m['thresholds']['min'] for m in metrics_config]
target_values = [m['thresholds']['target'] for m in metrics_config]
# Crear y configurar gráfico
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='polar')
# Configurar radar
angles = [n / float(len(categories)) * 2 * np.pi for n in range(len(categories))]
angles += angles[:1]
values_current += values_current[:1]
min_values += min_values[:1]
target_values += target_values[:1]
# Configurar ejes
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, fontsize=10)
circle_ticks = np.arange(0, 1.1, 0.2)
ax.set_yticks(circle_ticks)
ax.set_yticklabels([f'{tick:.1f}' for tick in circle_ticks], fontsize=8)
ax.set_ylim(0, 1)
# Dibujar áreas de umbrales
ax.plot(angles, min_values, '#e74c3c', linestyle='--', linewidth=1,
label='Mínimo', alpha=0.5)
ax.plot(angles, target_values, '#2ecc71', linestyle='--', linewidth=1,
label='Meta', alpha=0.5)
ax.fill_between(angles, target_values, [1]*len(angles),
color='#2ecc71', alpha=0.1)
ax.fill_between(angles, [0]*len(angles), min_values,
color='#e74c3c', alpha=0.1)
# Si hay línea base, dibujarla primero
if baseline_metrics is not None:
values_baseline = [baseline_metrics[m['key']]['normalized_score']
for m in metrics_config]
values_baseline += values_baseline[:1]
ax.plot(angles, values_baseline, '#888888', linewidth=2,
label='Línea base', linestyle='--')
ax.fill(angles, values_baseline, '#888888', alpha=0.1)
# Dibujar valores actuales
label = 'Actual' if baseline_metrics else 'Tu escritura'
color = '#3498db' if baseline_metrics else '#3498db'
ax.plot(angles, values_current, color, linewidth=2, label=label)
ax.fill(angles, values_current, color, alpha=0.2)
# Ajustar leyenda
legend_handles = []
if baseline_metrics:
legend_handles.extend([
plt.Line2D([], [], color='#888888', linestyle='--',
label='Línea base'),
plt.Line2D([], [], color='#3498db', label='Actual')
])
else:
legend_handles.extend([
plt.Line2D([], [], color='#3498db', label='Tu escritura')
])
legend_handles.extend([
plt.Line2D([], [], color='#e74c3c', linestyle='--', label='Mínimo'),
plt.Line2D([], [], color='#2ecc71', linestyle='--', label='Meta')
])
ax.legend(
handles=legend_handles,
loc='upper right',
bbox_to_anchor=(1.3, 1.1),
fontsize=10,
frameon=True,
facecolor='white',
edgecolor='none',
shadow=True
)
plt.tight_layout()
st.pyplot(fig)
plt.close()
except Exception as e:
logger.error(f"Error mostrando gráfico radar: {str(e)}")
st.error("Error al mostrar el gráfico")
#######################################