v3 / modules /studentact /current_situation_interface.py
AIdeaText's picture
Update modules/studentact/current_situation_interface.py
2c67534 verified
raw
history blame
24.3 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 columnas de texto y resultados distribuidas equitativamente.
"""
# Inicializar estados si no existen
if 'text_base' not in st.session_state:
st.session_state.text_base = ""
if 'text_iteration' not in st.session_state:
st.session_state.text_iteration = ""
if 'show_base_results' not in st.session_state:
st.session_state.show_base_results = False
if 'show_iteration_results' not in st.session_state:
st.session_state.show_iteration_results = False
if 'base_doc' not in st.session_state:
st.session_state.base_doc = None
if 'iteration_doc' not in st.session_state:
st.session_state.iteration_doc = None
if 'base_metrics' not in st.session_state:
st.session_state.base_metrics = None
if 'iteration_metrics' not in st.session_state:
st.session_state.iteration_metrics = None
try:
# Container principal con cuatro columnas
with st.container():
# Crear las cuatro columnas con distribución equitativa
text_base_col, metrics_base_col, text_iter_col, metrics_iter_col = st.columns([3,2,3,2], gap="small")
# COLUMNA 1: Texto Base
with text_base_col:
text_base = st.text_area(
t.get('input_prompt_base', "Escribe o pega el texto base aquí:"),
height=400,
key="text_area_base",
value=st.session_state.text_base,
help="Este texto servirá como línea base para la comparación"
)
# Manejar cambios en el texto base
if text_base != st.session_state.text_base:
st.session_state.text_base = text_base
st.session_state.show_base_results = False
if st.button(
t.get('analyze_base_button', "Establecer Línea Base"),
type="primary",
disabled=not text_base.strip(),
use_container_width=True,
):
try:
with st.spinner(t.get('processing_base', "Analizando línea base...")):
doc = nlp_models[lang_code](text_base)
metrics = analyze_text_dimensions(doc)
storage_success = store_current_situation_result(
username=st.session_state.username,
text=text_base,
metrics=metrics,
feedback=None,
analysis_type='base'
)
if not storage_success:
logger.warning("No se pudo guardar el análisis base en la base de datos")
st.session_state.base_doc = doc
st.session_state.base_metrics = metrics
st.session_state.show_base_results = True
except Exception as e:
logger.error(f"Error en análisis base: {str(e)}")
st.error(t.get('analysis_error', "Error al analizar el texto base"))
# COLUMNA 2: Resultados Base
with metrics_base_col:
if st.session_state.show_base_results and st.session_state.base_metrics is not None:
st.markdown("### Métricas Base")
display_results(
metrics=st.session_state.base_metrics,
text_type=st.session_state.get('current_text_type', 'student_essay')
)
# COLUMNA 3: Texto Iteración
with text_iter_col:
text_iter = st.text_area(
t.get('input_prompt_iter', "Escribe la nueva versión del texto:"),
height=400,
key="text_area_iter",
value=st.session_state.text_iteration,
help="Este texto será comparado con la línea base",
disabled=not st.session_state.show_base_results
)
# Manejar cambios en el texto de iteración
if text_iter != st.session_state.text_iteration:
st.session_state.text_iteration = text_iter
st.session_state.show_iteration_results = False
if st.button(
t.get('analyze_iter_button', "Analizar Iteración"),
type="primary",
disabled=not text_iter.strip() or not st.session_state.show_base_results,
use_container_width=True,
):
try:
with st.spinner(t.get('processing_iter', "Analizando iteración...")):
doc = nlp_models[lang_code](text_iter)
metrics = analyze_text_dimensions(doc)
storage_success = store_current_situation_result(
username=st.session_state.username,
text=text_iter,
metrics=metrics,
feedback=None,
analysis_type='iteration'
)
if not storage_success:
logger.warning("No se pudo guardar el análisis de iteración en la base de datos")
st.session_state.iteration_doc = doc
st.session_state.iteration_metrics = metrics
st.session_state.show_iteration_results = True
except Exception as e:
logger.error(f"Error en análisis de iteración: {str(e)}")
st.error(t.get('analysis_error', "Error al analizar la iteración"))
# COLUMNA 4: Resultados Iteración
with metrics_iter_col:
if st.session_state.show_iteration_results and st.session_state.iteration_metrics is not None:
st.markdown("### Métricas Iteración")
display_results(
metrics=st.session_state.iteration_metrics,
text_type=st.session_state.get('current_text_type', 'student_essay')
)
except Exception as e:
logger.error(f"Error en interfaz principal: {str(e)}")
st.error("Ocurrió un 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")
#######################################