Spaces:
Running
Running
| """ | |
| APLICACIÓN WEB GRADIO - VISUALIZACIONES ODS | |
| ============================================ | |
| Aplicación interactiva que permite explorar las 10 visualizaciones | |
| de análisis de similaridad ODS a través de una interfaz web amigable. | |
| Características: | |
| - Interfaz con pestañas para cada visualización | |
| - Explicaciones integradas para público general | |
| - Visualizaciones interactivas (HTML) y estáticas (PNG) | |
| - Estadísticas en tiempo real | |
| - Diseño responsivo y profesional | |
| Autor: Daniel Sandvoval | |
| Fecha: Noviembre 2025 | |
| """ | |
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| from pathlib import Path | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from plotly.subplots import make_subplots | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| from src.embeddings.modelos_nlp_db import search | |
| from src.embeddings.mass_modelos_nlp_db import search_mass | |
| # from src.embeddings.llm_clasificador_HF import procesar_lote | |
| from huggingface_hub import InferenceClient | |
| # import pandas as pd | |
| import re | |
| import time | |
| import pytz | |
| from datetime import datetime, timedelta | |
| # Importar funciones de visualización | |
| import sys | |
| # sys.path.insert(0, '/home/claude') | |
| from src.visualization.visualizaciones_ods import ( | |
| cargar_datos, | |
| viz_1_distribucion_por_ods, | |
| viz_2_heatmap_ods_ranking, | |
| viz_3_scatter_3d_interactivo, | |
| viz_4_radar_chart_ods, | |
| viz_5_sunburst_jerarquia, | |
| viz_6_top_indicadores_por_ods, | |
| viz_7_streamgraph_similaridad, | |
| viz_8_violin_plot_ods, | |
| viz_9_dashboard_metricas, | |
| viz_10_matriz_transicion, | |
| viz_19_resumen_tags, | |
| viz_20_pareto_ods, | |
| viz_21_ranking_mixto_masivo, | |
| viz_22_composicion_score_mixto, | |
| viz_23_nube_palabras_simple, | |
| viz_24_header_conecta_ods, | |
| analisis_estadistico | |
| ) | |
| # ============================================================================ | |
| # CONFIGURACIÓN GLOBAL | |
| # ============================================================================ | |
| import os | |
| import base64 | |
| levels = ['ODS_ID','META_ID','INDICADOR_ID'] | |
| def generarFecha(): | |
| bogota_timezone = pytz.timezone('America/Bogota') | |
| bogota_date = datetime.now(bogota_timezone) | |
| fecha = bogota_date.strftime('%Y_%m_%d_%H_%M_%S') | |
| anio = bogota_date.strftime('%Y') | |
| mes = bogota_date.strftime('%m') | |
| dia = bogota_date.strftime('%d') | |
| hora = bogota_date.strftime('%H') | |
| minuto = bogota_date.strftime('%M') | |
| segundo = bogota_date.strftime('%S') | |
| return fecha, anio, mes, dia, hora, minuto, segundo | |
| def actualizar_fecha(): | |
| fecha, anio, mes, dia, hora, minuto, segundo = generarFecha() | |
| return f"Fecha de consulta: {anio}-{mes}-{dia} {hora}:{minuto}" | |
| def convertir_logo_a_base64(logo_path): | |
| """Convierte un logo a base64 para incrustar en HTML""" | |
| # try: | |
| # rutas_posibles = [ | |
| # logo_path, | |
| # os.path.join(os.path.dirname(__file__), logo_path), | |
| # os.path.join('/mnt/user-data/outputs', logo_path), | |
| # ] | |
| # for ruta in rutas_posibles: | |
| # if os.path.exists(ruta): | |
| # with open(ruta, "rb") as image_file: | |
| # encoded = base64.b64encode(image_file.read()).decode() | |
| # return f"data:image/png;base64,{encoded}" | |
| # print(f"⚠️ Logo no encontrado: {logo_path}") | |
| # return "" | |
| # except Exception as e: | |
| # print(f"⚠️ Error al cargar logo: {e}") | |
| # return "" | |
| ruta = Path('config/institucional/logos') | |
| with open(Path(f'{ruta}/{logo_path}'), "rb") as image_file: | |
| encoded = base64.b64encode(image_file.read()).decode() | |
| return f"data:image/png;base64,{encoded}" | |
| # Cargar logos una sola vez al iniciar | |
| print("Cargando logos institucionales...") | |
| LOGO_GOBIERNO = convertir_logo_a_base64("/institucional/GOBIERNO-DE-COLOMBIA_HORIZONTAL.webp") | |
| LOGO_FONDO = convertir_logo_a_base64("/institucional/LOGO MPTF (ESP).webp") | |
| if LOGO_GOBIERNO and LOGO_FONDO: | |
| print("✅ Logos cargados correctamente") | |
| else: | |
| print("⚠️ Algunos logos no se pudieron cargar") | |
| # Diccionario de colores oficiales de los ODS (Fuente: Guías de la ONU) | |
| colores_ods = { | |
| "1": "#E5243B", # Red - ODS 1: Fin de la Pobreza | |
| "2": "#DDA63A", # Mustard - ODS 2: Hambre Cero | |
| "3": "#4C9F38", # Kelly Green - ODS 3: Salud y Bienestar | |
| "4": "#C5192D", # Dark Red - ODS 4: Educación de Calidad | |
| "5": "#FF3A21", # Red Orange - ODS 5: Igualdad de Género | |
| "6": "#26BDE2", # Bright Blue - ODS 6: Agua Limpia y Saneamiento | |
| "7": "#FCC30B", # Yellow - ODS 7: Energía Asequible y No Contaminante | |
| "8": "#A21942", # Burgundy Red - ODS 8: Trabajo Decente y Crecimiento Económico | |
| "9": "#FD6925", # Orange - ODS 9: Industria, Innovación e Infraestructura | |
| "10": "#DD1367", # Magenta - ODS 10: Reducción de las Desigualdades | |
| "11": "#FD9D24", # Golden Yellow - ODS 11: Ciudades y Comunidades Sostenibles | |
| "12": "#BF8B2E", # Dark Mustard - ODS 12: Producción y Consumo Responsables | |
| "13": "#3F7E44", # Dark Green - ODS 13: Acción por el Clima | |
| "14": "#0A97D9", # Blue - ODS 14: Vida Submarina | |
| "15": "#56C02B", # Lime Green - ODS 15: Vida de Ecosistemas Terrestres | |
| "16": "#00689D", # Royal Blue - ODS 16: Paz, Justicia e Instituciones Sólidas | |
| "17": "#19486A", # Navy Blue - ODS 17: Alianzas para Lograr los Objetivos | |
| } | |
| # Color adicional para el logotipo general de los ODS | |
| color_logo_ods = "#009EDB" # Logo Blue | |
| dict_logos = { | |
| 'gobierno': convertir_logo_a_base64("/institucional/GOBIERNO-DE-COLOMBIA_HORIZONTAL.webp"), | |
| 'fondo_un': convertir_logo_a_base64("/institucional/LOGO MPTF (ESP).webp"), | |
| 'logo_conectaods': convertir_logo_a_base64("/institucional/LOGO-ODS-aprobado.webp"), | |
| 'ods_1': convertir_logo_a_base64("/ods/S-WEB-Goal-01.webp"), | |
| 'ods_2': convertir_logo_a_base64("/ods/S-WEB-Goal-02.webp"), | |
| 'ods_3': convertir_logo_a_base64("/ods/S-WEB-Goal-03.webp"), | |
| 'ods_4': convertir_logo_a_base64("/ods/S-WEB-Goal-04.webp"), | |
| 'ods_5': convertir_logo_a_base64("/ods/S-WEB-Goal-05.webp"), | |
| 'ods_6': convertir_logo_a_base64("/ods/S-WEB-Goal-06.webp"), | |
| 'ods_7': convertir_logo_a_base64("/ods/S-WEB-Goal-07.webp"), | |
| 'ods_8': convertir_logo_a_base64("/ods/S-WEB-Goal-08.webp"), | |
| 'ods_9': convertir_logo_a_base64("/ods/S-WEB-Goal-09.webp"), | |
| 'ods_10': convertir_logo_a_base64("/ods/S-WEB-Goal-10.webp"), | |
| 'ods_11': convertir_logo_a_base64("/ods/S-WEB-Goal-11.webp"), | |
| 'ods_12': convertir_logo_a_base64("/ods/S-WEB-Goal-12.webp"), | |
| 'ods_13': convertir_logo_a_base64("/ods/S-WEB-Goal-13.webp"), | |
| 'ods_14': convertir_logo_a_base64("/ods/S-WEB-Goal-14.webp"), | |
| 'ods_15': convertir_logo_a_base64("/ods/S-WEB-Goal-15.webp"), | |
| 'ods_16': convertir_logo_a_base64("/ods/S-WEB-Goal-16.webp"), | |
| 'ods_17': convertir_logo_a_base64("/ods/S-WEB-Goal-17.webp"), | |
| # Metas ODS | |
| 'meta_1.1': convertir_logo_a_base64("/metas/MC_Target_1.1.webp"), | |
| 'meta_1.2': convertir_logo_a_base64("/metas/MC_Target_1.2.webp"), | |
| 'meta_1.3': convertir_logo_a_base64("/metas/MC_Target_1.3.webp"), | |
| 'meta_1.4': convertir_logo_a_base64("/metas/MC_Target_1.4.webp"), | |
| 'meta_1.5': convertir_logo_a_base64("/metas/MC_Target_1.5.webp"), | |
| 'meta_1.A': convertir_logo_a_base64("/metas/MC_Target_1.A.webp"), | |
| 'meta_1.B': convertir_logo_a_base64("/metas/MC_Target_1.B.webp"), | |
| 'meta_2.1': convertir_logo_a_base64("/metas/MC_Target_2.1.webp"), | |
| 'meta_2.2': convertir_logo_a_base64("/metas/MC_Target_2.2.webp"), | |
| 'meta_2.3': convertir_logo_a_base64("/metas/MC_Target_2.3.webp"), | |
| 'meta_2.4': convertir_logo_a_base64("/metas/MC_Target_2.4.webp"), | |
| 'meta_2.5': convertir_logo_a_base64("/metas/MC_Target_2.5.webp"), | |
| 'meta_2.A': convertir_logo_a_base64("/metas/MC_Target_2.A.webp"), | |
| 'meta_2.B': convertir_logo_a_base64("/metas/MC_Target_2.B.webp"), | |
| 'meta_2.C': convertir_logo_a_base64("/metas/MC_Target_2.C.webp"), | |
| 'meta_3.1': convertir_logo_a_base64("/metas/MC_Target_3.1.webp"), | |
| 'meta_3.2': convertir_logo_a_base64("/metas/MC_Target_3.2.webp"), | |
| 'meta_3.3': convertir_logo_a_base64("/metas/MC_Target_3.3.webp"), | |
| 'meta_3.4': convertir_logo_a_base64("/metas/MC_Target_3.4.webp"), | |
| 'meta_3.5': convertir_logo_a_base64("/metas/MC_Target_3.5.webp"), | |
| 'meta_3.6': convertir_logo_a_base64("/metas/MC_Target_3.6.webp"), | |
| 'meta_3.7': convertir_logo_a_base64("/metas/MC_Target_3.7.webp"), | |
| 'meta_3.8': convertir_logo_a_base64("/metas/MC_Target_3.8.webp"), | |
| 'meta_3.9': convertir_logo_a_base64("/metas/MC_Target_3.9.webp"), | |
| 'meta_3.A': convertir_logo_a_base64("/metas/MC_Target_3.A.webp"), | |
| 'meta_3.B': convertir_logo_a_base64("/metas/MC_Target_3.B.webp"), | |
| 'meta_3.C': convertir_logo_a_base64("/metas/MC_Target_3.C.webp"), | |
| 'meta_3.D': convertir_logo_a_base64("/metas/MC_Target_3.D.webp"), | |
| 'meta_4.1': convertir_logo_a_base64("/metas/MC_Target_4.1.webp"), | |
| 'meta_4.2': convertir_logo_a_base64("/metas/MC_Target_4.2.webp"), | |
| 'meta_4.3': convertir_logo_a_base64("/metas/MC_Target_4.3.webp"), | |
| 'meta_4.4': convertir_logo_a_base64("/metas/MC_Target_4.4.webp"), | |
| 'meta_4.5': convertir_logo_a_base64("/metas/MC_Target_4.5.webp"), | |
| 'meta_4.6': convertir_logo_a_base64("/metas/MC_Target_4.6.webp"), | |
| 'meta_4.7': convertir_logo_a_base64("/metas/MC_Target_4.7.webp"), | |
| 'meta_4.A': convertir_logo_a_base64("/metas/MC_Target_4.A.webp"), | |
| 'meta_4.B': convertir_logo_a_base64("/metas/MC_Target_4.B.webp"), | |
| 'meta_4.C': convertir_logo_a_base64("/metas/MC_Target_4.C.webp"), | |
| 'meta_5.1': convertir_logo_a_base64("/metas/MC_Target_5.1.webp"), | |
| 'meta_5.2': convertir_logo_a_base64("/metas/MC_Target_5.2.webp"), | |
| 'meta_5.3': convertir_logo_a_base64("/metas/MC_Target_5.3.webp"), | |
| 'meta_5.4': convertir_logo_a_base64("/metas/MC_Target_5.4.webp"), | |
| 'meta_5.5': convertir_logo_a_base64("/metas/MC_Target_5.5.webp"), | |
| 'meta_5.6': convertir_logo_a_base64("/metas/MC_Target_5.6.webp"), | |
| 'meta_5.A': convertir_logo_a_base64("/metas/MC_Target_5.A.webp"), | |
| 'meta_5.B': convertir_logo_a_base64("/metas/MC_Target_5.B.webp"), | |
| 'meta_5.C': convertir_logo_a_base64("/metas/MC_Target_5.C.webp"), | |
| 'meta_6.1': convertir_logo_a_base64("/metas/MC_Target_6.1.webp"), | |
| 'meta_6.2': convertir_logo_a_base64("/metas/MC_Target_6.2.webp"), | |
| 'meta_6.3': convertir_logo_a_base64("/metas/MC_Target_6.3.webp"), | |
| 'meta_6.4': convertir_logo_a_base64("/metas/MC_Target_6.4.webp"), | |
| 'meta_6.5': convertir_logo_a_base64("/metas/MC_Target_6.5.webp"), | |
| 'meta_6.6': convertir_logo_a_base64("/metas/MC_Target_6.6.webp"), | |
| 'meta_6.A': convertir_logo_a_base64("/metas/MC_Target_6.A.webp"), | |
| 'meta_6.B': convertir_logo_a_base64("/metas/MC_Target_6.B.webp"), | |
| 'meta_7.1': convertir_logo_a_base64("/metas/MC_Target_7.1.webp"), | |
| 'meta_7.2': convertir_logo_a_base64("/metas/MC_Target_7.2.webp"), | |
| 'meta_7.3': convertir_logo_a_base64("/metas/MC_Target_7.3.webp"), | |
| 'meta_7.A': convertir_logo_a_base64("/metas/MC_Target_7.A.webp"), | |
| 'meta_7.B': convertir_logo_a_base64("/metas/MC_Target_7.B.webp"), | |
| 'meta_8.1': convertir_logo_a_base64("/metas/MC_Target_8.1.webp"), | |
| 'meta_8.2': convertir_logo_a_base64("/metas/MC_Target_8.2.webp"), | |
| 'meta_8.3': convertir_logo_a_base64("/metas/MC_Target_8.3.webp"), | |
| 'meta_8.4': convertir_logo_a_base64("/metas/MC_Target_8.4.webp"), | |
| 'meta_8.5': convertir_logo_a_base64("/metas/MC_Target_8.5.webp"), | |
| 'meta_8.6': convertir_logo_a_base64("/metas/MC_Target_8.6.webp"), | |
| 'meta_8.7': convertir_logo_a_base64("/metas/MC_Target_8.7.webp"), | |
| 'meta_8.8': convertir_logo_a_base64("/metas/MC_Target_8.8.webp"), | |
| 'meta_8.9': convertir_logo_a_base64("/metas/MC_Target_8.9.webp"), | |
| 'meta_8.10': convertir_logo_a_base64("/metas/MC_Target_8.10.webp"), | |
| 'meta_8.A': convertir_logo_a_base64("/metas/MC_Target_8.A.webp"), | |
| 'meta_8.B': convertir_logo_a_base64("/metas/MC_Target_8.B.webp"), | |
| 'meta_9.1': convertir_logo_a_base64("/metas/MC_Target_9.1.webp"), | |
| 'meta_9.2': convertir_logo_a_base64("/metas/MC_Target_9.2.webp"), | |
| 'meta_9.3': convertir_logo_a_base64("/metas/MC_Target_9.3.webp"), | |
| 'meta_9.4': convertir_logo_a_base64("/metas/MC_Target_9.4.webp"), | |
| 'meta_9.5': convertir_logo_a_base64("/metas/MC_Target_9.5.webp"), | |
| 'meta_9.A': convertir_logo_a_base64("/metas/MC_Target_9.A.webp"), | |
| 'meta_9.B': convertir_logo_a_base64("/metas/MC_Target_9.B.webp"), | |
| 'meta_9.C': convertir_logo_a_base64("/metas/MC_Target_9.C.webp"), | |
| 'meta_10.1': convertir_logo_a_base64("/metas/MC_Target_10.1.webp"), | |
| 'meta_10.2': convertir_logo_a_base64("/metas/MC_Target_10.2.webp"), | |
| 'meta_10.3': convertir_logo_a_base64("/metas/MC_Target_10.3.webp"), | |
| 'meta_10.4': convertir_logo_a_base64("/metas/MC_Target_10.4.webp"), | |
| 'meta_10.5': convertir_logo_a_base64("/metas/MC_Target_10.5.webp"), | |
| 'meta_10.6': convertir_logo_a_base64("/metas/MC_Target_10.6.webp"), | |
| 'meta_10.7': convertir_logo_a_base64("/metas/MC_Target_10.7.webp"), | |
| 'meta_10.A': convertir_logo_a_base64("/metas/MC_Target_10.A.webp"), | |
| 'meta_10.B': convertir_logo_a_base64("/metas/MC_Target_10.B.webp"), | |
| 'meta_10.C': convertir_logo_a_base64("/metas/MC_Target_10.C.webp"), | |
| 'meta_11.1': convertir_logo_a_base64("/metas/MC_Target_11.1.webp"), | |
| 'meta_11.2': convertir_logo_a_base64("/metas/MC_Target_11.2.webp"), | |
| 'meta_11.3': convertir_logo_a_base64("/metas/MC_Target_11.3.webp"), | |
| 'meta_11.4': convertir_logo_a_base64("/metas/MC_Target_11.4.webp"), | |
| 'meta_11.5': convertir_logo_a_base64("/metas/MC_Target_11.5.webp"), | |
| 'meta_11.6': convertir_logo_a_base64("/metas/MC_Target_11.6.webp"), | |
| 'meta_11.7': convertir_logo_a_base64("/metas/MC_Target_11.7.webp"), | |
| 'meta_11.A': convertir_logo_a_base64("/metas/MC_Target_11.A.webp"), | |
| 'meta_11.B': convertir_logo_a_base64("/metas/MC_Target_11.B.webp"), | |
| 'meta_11.C': convertir_logo_a_base64("/metas/MC_Target_11.C.webp"), | |
| 'meta_12.1': convertir_logo_a_base64("/metas/MC_Target_12.1.webp"), | |
| 'meta_12.2': convertir_logo_a_base64("/metas/MC_Target_12.2.webp"), | |
| 'meta_12.3': convertir_logo_a_base64("/metas/MC_Target_12.3.webp"), | |
| 'meta_12.4': convertir_logo_a_base64("/metas/MC_Target_12.4.webp"), | |
| 'meta_12.5': convertir_logo_a_base64("/metas/MC_Target_12.5.webp"), | |
| 'meta_12.6': convertir_logo_a_base64("/metas/MC_Target_12.6.webp"), | |
| 'meta_12.7': convertir_logo_a_base64("/metas/MC_Target_12.7.webp"), | |
| 'meta_12.8': convertir_logo_a_base64("/metas/MC_Target_12.8.webp"), | |
| 'meta_12.A': convertir_logo_a_base64("/metas/MC_Target_12.A.webp"), | |
| 'meta_12.B': convertir_logo_a_base64("/metas/MC_Target_12.B.webp"), | |
| 'meta_12.C': convertir_logo_a_base64("/metas/MC_Target_12.C.webp"), | |
| 'meta_13.1': convertir_logo_a_base64("/metas/MC_Target_13.1.webp"), | |
| 'meta_13.2': convertir_logo_a_base64("/metas/MC_Target_13.2.webp"), | |
| 'meta_13.3': convertir_logo_a_base64("/metas/MC_Target_13.3.webp"), | |
| 'meta_13.A': convertir_logo_a_base64("/metas/MC_Target_13.A.webp"), | |
| 'meta_13.B': convertir_logo_a_base64("/metas/MC_Target_13.B.webp"), | |
| 'meta_14.1': convertir_logo_a_base64("/metas/MC_Target_14.1.webp"), | |
| 'meta_14.2': convertir_logo_a_base64("/metas/MC_Target_14.2.webp"), | |
| 'meta_14.3': convertir_logo_a_base64("/metas/MC_Target_14.3.webp"), | |
| 'meta_14.4': convertir_logo_a_base64("/metas/MC_Target_14.4.webp"), | |
| 'meta_14.5': convertir_logo_a_base64("/metas/MC_Target_14.5.webp"), | |
| 'meta_14.6': convertir_logo_a_base64("/metas/MC_Target_14.6.webp"), | |
| 'meta_14.7': convertir_logo_a_base64("/metas/MC_Target_14.7.webp"), | |
| 'meta_14.A': convertir_logo_a_base64("/metas/MC_Target_14.A.webp"), | |
| 'meta_14.B': convertir_logo_a_base64("/metas/MC_Target_14.B.webp"), | |
| 'meta_14.C': convertir_logo_a_base64("/metas/MC_Target_14.C.webp"), | |
| 'meta_15.1': convertir_logo_a_base64("/metas/MC_Target_15.1.webp"), | |
| 'meta_15.2': convertir_logo_a_base64("/metas/MC_Target_15.2.webp"), | |
| 'meta_15.3': convertir_logo_a_base64("/metas/MC_Target_15.3.webp"), | |
| 'meta_15.4': convertir_logo_a_base64("/metas/MC_Target_15.4.webp"), | |
| 'meta_15.5': convertir_logo_a_base64("/metas/MC_Target_15.5.webp"), | |
| 'meta_15.6': convertir_logo_a_base64("/metas/MC_Target_15.6.webp"), | |
| 'meta_15.7': convertir_logo_a_base64("/metas/MC_Target_15.7.webp"), | |
| 'meta_15.8': convertir_logo_a_base64("/metas/MC_Target_15.8.webp"), | |
| 'meta_15.9': convertir_logo_a_base64("/metas/MC_Target_15.9.webp"), | |
| 'meta_15.A': convertir_logo_a_base64("/metas/MC_Target_15.A.webp"), | |
| 'meta_15.B': convertir_logo_a_base64("/metas/MC_Target_15.B.webp"), | |
| 'meta_15.C': convertir_logo_a_base64("/metas/MC_Target_15.C.webp"), | |
| 'meta_16.1': convertir_logo_a_base64("/metas/MC_Target_16.1.webp"), | |
| 'meta_16.2': convertir_logo_a_base64("/metas/MC_Target_16.2.webp"), | |
| 'meta_16.3': convertir_logo_a_base64("/metas/MC_Target_16.3.webp"), | |
| 'meta_16.4': convertir_logo_a_base64("/metas/MC_Target_16.4.webp"), | |
| 'meta_16.5': convertir_logo_a_base64("/metas/MC_Target_16.5.webp"), | |
| 'meta_16.6': convertir_logo_a_base64("/metas/MC_Target_16.6.webp"), | |
| 'meta_16.7': convertir_logo_a_base64("/metas/MC_Target_16.7.webp"), | |
| 'meta_16.8': convertir_logo_a_base64("/metas/MC_Target_16.8.webp"), | |
| 'meta_16.9': convertir_logo_a_base64("/metas/MC_Target_16.9.webp"), | |
| 'meta_16.10': convertir_logo_a_base64("/metas/MC_Target_16.10.webp"), | |
| 'meta_16.A': convertir_logo_a_base64("/metas/MC_Target_16.A.webp"), | |
| 'meta_16.B': convertir_logo_a_base64("/metas/MC_Target_16.B.webp"), | |
| 'meta_17.1': convertir_logo_a_base64("/metas/MC_Target_17.1.webp"), | |
| 'meta_17.2': convertir_logo_a_base64("/metas/MC_Target_17.2.webp"), | |
| 'meta_17.3': convertir_logo_a_base64("/metas/MC_Target_17.3.webp"), | |
| 'meta_17.4': convertir_logo_a_base64("/metas/MC_Target_17.4.webp"), | |
| 'meta_17.5': convertir_logo_a_base64("/metas/MC_Target_17.5.webp"), | |
| 'meta_17.6': convertir_logo_a_base64("/metas/MC_Target_17.6.webp"), | |
| 'meta_17.7': convertir_logo_a_base64("/metas/MC_Target_17.7.webp"), | |
| 'meta_17.8': convertir_logo_a_base64("/metas/MC_Target_17.8.webp"), | |
| 'meta_17.9': convertir_logo_a_base64("/metas/MC_Target_17.9.webp"), | |
| 'meta_17.10': convertir_logo_a_base64("/metas/MC_Target_17.10.webp"), | |
| 'meta_17.11': convertir_logo_a_base64("/metas/MC_Target_17.11.webp"), | |
| 'meta_17.12': convertir_logo_a_base64("/metas/MC_Target_17.12.webp"), | |
| 'meta_17.13': convertir_logo_a_base64("/metas/MC_Target_17.13.webp"), | |
| 'meta_17.14': convertir_logo_a_base64("/metas/MC_Target_17.14.webp"), | |
| 'meta_17.15': convertir_logo_a_base64("/metas/MC_Target_17.15.webp"), | |
| 'meta_17.16': convertir_logo_a_base64("/metas/MC_Target_17.16.webp"), | |
| 'meta_17.17': convertir_logo_a_base64("/metas/MC_Target_17.17.webp"), | |
| 'meta_17.18': convertir_logo_a_base64("/metas/MC_Target_17.18.webp"), | |
| 'meta_17.19': convertir_logo_a_base64("/metas/MC_Target_17.19.webp"), | |
| } | |
| # Ruta al archivo de datos | |
| # # RUTA_DATOS = '/mnt/user-data/uploads/indicadores_markdown.txt' | |
| # RUTA_DATOS = '/content/drive/MyDrive/Compartida/06_Desarrollo de la herramienta IA/01_MPTF /archivos_trabajo/app_visualizaciones/indicadores_markdown.txt' | |
| # # Cargar datos globalmente para toda la app | |
| # try: | |
| # df_global = cargar_datos(RUTA_DATOS) | |
| DATOS_CARGADOS = True | |
| # print(f"✓ Datos cargados: {len(df_global)} registros") | |
| # except Exception as e: | |
| # df_global = None | |
| # DATOS_CARGADOS = False | |
| # print(f"✗ Error al cargar datos: {e}") | |
| # Estilos CSS personalizados | |
| CUSTOM_CSS = """ | |
| .gradio-container { | |
| font-family: 'Arial', sans-serif; | |
| } | |
| .explanation-box { | |
| background-color: #E8F4F8; | |
| padding: 20px; | |
| border-radius: 10px; | |
| border-left: 5px solid #2E5090; | |
| margin: 10px 0; | |
| } | |
| .stats-box { | |
| background-color: #009EDB; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border: 2px solid #FFD700; | |
| margin: 10px 0; | |
| } | |
| .important-box { | |
| background-color: #FFE6E6; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border-left: 5px solid #C00000; | |
| margin: 10px 0; | |
| } | |
| h1, h2, h3 { | |
| color: #2E5090; | |
| } | |
| .tab-nav button { | |
| font-size: 16px; | |
| padding: 10px 20px; | |
| } | |
| /* ESTILOS PARA HEADER CON LOGOS INSTITUCIONALES */ | |
| .header-institucional { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 20px 40px; | |
| background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 50%, #f8f9fa 100%); | |
| border-bottom: 4px solid #003DA5; | |
| margin-bottom: 25px; | |
| box-shadow: 0 3px 10px rgba(0,0,0,0.08); | |
| } | |
| .logo-institucional { | |
| height: 40px; | |
| width: auto; | |
| object-fit: contain; | |
| } | |
| .titulo-institucional { | |
| flex: 1; | |
| text-align: center; | |
| padding: 0 30px; | |
| } | |
| .titulo-institucional h1 { | |
| margin: 0; | |
| color: #003DA5 !important; | |
| font-size: 28px; | |
| font-weight: 700; | |
| } | |
| .logo-ods-tbl { | |
| height: 60px; | |
| width: auto; | |
| object-fit: contain; | |
| } | |
| @media (max-width: 768px) { | |
| .header-institucional { | |
| padding: 15px 20px; | |
| flex-direction: column; | |
| gap: 15px; | |
| } | |
| .logo-institucional { | |
| height: 50px; | |
| } | |
| } | |
| """ | |
| # ============================================================================ | |
| # FUNCIONES DE CONVERSIÓN DE FIGURAS | |
| # ============================================================================ | |
| def plotly_to_html(fig): | |
| """Convierte figura Plotly a HTML para mostrar en Gradio""" | |
| return fig.to_html(include_plotlyjs='cdn', full_html=False) | |
| def matplotlib_to_file(fig, filename): | |
| """Convierte figura Matplotlib a archivo temporal""" | |
| import tempfile | |
| import os | |
| # Crear directorio temporal si no existe | |
| temp_dir = tempfile.gettempdir() | |
| filepath = os.path.join(temp_dir, filename) | |
| # Guardar la figura | |
| fig.savefig(filepath, format='png', dpi=150, bbox_inches='tight') | |
| plt.close(fig) | |
| return filepath | |
| # ============================================================================ | |
| ##### FUNCIONES DE PROCESAMIENTO DE CONSULTA MASIVA | |
| # ============================================================================ | |
| def calcular_peso_ranking_inverso(rank, nivel): | |
| """ | |
| Peso decrece linealmente: Rank 1 = peso 10, Rank 10 = peso 1 | |
| """ | |
| if nivel == 'ods': | |
| max_rank = 17 | |
| elif nivel == 'meta': | |
| max_rank = 169 | |
| elif nivel == 'indicador': | |
| max_rank = 244 | |
| return max_rank - rank + 1 | |
| def calcular_corte_pareto(df_iniciativa, nivel, umbral_pareto=0.8, mass = False): | |
| """ | |
| Determina el top_rank usando Pareto (80/20) | |
| Args: | |
| df_iniciativa: DataFrame de una iniciativa con similaridad | |
| umbral_pareto: % acumulado para corte (default 0.8 = 80%), puede ser string o float | |
| Returns: | |
| top_rank: Número de elementos que conforman el 80% | |
| """ | |
| # Convertir umbral_pareto a float si viene como string | |
| umbral_pareto = float(umbral_pareto) | |
| if mass: | |
| df_init = df_iniciativa.copy() | |
| # Ordenar por similaridad | |
| df_sorted = df_init.sort_values(f'score', ascending=False).reset_index(drop=True) | |
| # Se aplica calculo exponencial para acentuar y diferenciar los pesos | |
| df_sorted[f'{nivel}_similaridad_cos_exp3'] = df_sorted[f'score'] ** 3 | |
| else: | |
| # Ordenar por similaridad descendente | |
| df_sorted = df_iniciativa.sort_values(f'{nivel}_similaridad_cos', ascending=False).copy() | |
| df_sorted[f'{nivel}_similaridad_cos_exp3'] = df_sorted[f'{nivel}_similaridad_cos'] ** 3 | |
| # Normalizar la columna ods_similaridad_cos_exp3 | |
| min_val = df_sorted[f'{nivel}_similaridad_cos_exp3'].min() | |
| max_val = df_sorted[f'{nivel}_similaridad_cos_exp3'].max() | |
| if max_val - min_val > 0: | |
| df_sorted[f'{nivel}_similaridad_cos_exp3_norm'] = (df_sorted[f'{nivel}_similaridad_cos_exp3'] - min_val) / (max_val - min_val) | |
| else: | |
| df_sorted[f'{nivel}_similaridad_cos_exp3_norm'] = 0.0 # Handle case where all values are the same | |
| # Aplicar Ranking Inverso | |
| df_sorted['peso'] = df_sorted[f'{nivel}_rank'].apply(lambda r: calcular_peso_ranking_inverso(r, nivel)) | |
| df_sorted['similaridad_ponderada'] = df_sorted[f'{nivel}_similaridad_cos_exp3_norm'] * df_sorted['peso'] | |
| # Calcular suma total | |
| if mass: | |
| total_similaridad = df_sorted[f'score'].sum() | |
| else: | |
| total_similaridad = df_sorted[f'{nivel}_similaridad_cos'].sum() | |
| total_similaridad_ponderda = df_sorted['similaridad_ponderada'].sum() | |
| # Calcular acumulado | |
| if mass: | |
| df_sorted['acumulado'] = df_sorted[f'score'].cumsum() | |
| else: | |
| df_sorted['acumulado'] = df_sorted[f'{nivel}_similaridad_cos'].cumsum() | |
| df_sorted['porcentaje_acumulado'] = df_sorted['acumulado'] / total_similaridad | |
| # | |
| df_sorted['acumulado_ponderado'] = df_sorted['similaridad_ponderada'].cumsum() | |
| df_sorted['porcentaje_acumulado_ponderado'] = df_sorted['acumulado_ponderado'] / total_similaridad_ponderda | |
| # Encontrar punto de corte Pareto | |
| top_rank = (df_sorted['porcentaje_acumulado_ponderado'] <= umbral_pareto).sum() | |
| # Mínimo 3, máximo 10 | |
| top_rank = max(3, min(top_rank, 245)) | |
| return top_rank, df_sorted.head(top_rank) | |
| # Uso por iniciativa | |
| def aplicar_pareto_por_iniciativa(df_ods, nivel, umbral=0.8, mass = False): | |
| """ | |
| Aplica Pareto a cada iniciativa y marca el top | |
| """ | |
| resultados = [] | |
| for iniciativa_id in df_ods['INICIATIVA_ID'].unique(): | |
| df_init = df_ods[df_ods['INICIATIVA_ID'] == iniciativa_id] | |
| top_rank, df_top = calcular_corte_pareto(df_init, nivel, umbral, mass) | |
| # Marcar los que están en el top Pareto | |
| df_top['en_pareto'] = True | |
| df_top['pareto_rank'] = range(1, len(df_top) + 1) | |
| resultados.append(df_top) | |
| df_pareto = pd.concat(resultados, ignore_index=True) | |
| return df_pareto | |
| def analisis_global_con_pareto(df, #df_metas, df_indicadores, | |
| nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.50, rank_prop=0.15, mass = False): | |
| """ | |
| Análisis global usando Pareto para determinar top_rank por iniciativa | |
| Flujo: | |
| 1. Por cada iniciativa, calcula su corte Pareto | |
| 2. Solo usa esos ODS "críticos" para el análisis global | |
| 3. Aplica la estrategia de ponderación elegida | |
| """ | |
| # Convertir parámetros a tipos correctos | |
| umbral_pareto = float(umbral_pareto) | |
| # Validar que DataFrame no esté vacío | |
| if df is None or df.empty: | |
| return gr.update(value=pd.DataFrame()), gr.update(value=pd.DataFrame()) | |
| try: | |
| # Paso 1: Aplicar Pareto a cada iniciativa | |
| df_pareto = aplicar_pareto_por_iniciativa(df, nivel, umbral_pareto, mass) | |
| print(f"📊 Análisis con Pareto (umbral {umbral_pareto*100}%):") | |
| print(f" Total registros originales: {len(df)}") | |
| print(f" Registros en zona Pareto: {len(df_pareto)}") | |
| print(f" Reducción: {(1 - len(df_pareto)/len(df))*100:.1f}%") | |
| # Paso 2: Análisis global solo con elementos Pareto | |
| if metodo == 'ranking_inverso': | |
| df_pareto['peso'] = 11 - df_pareto['pareto_rank'] | |
| df_pareto['score'] = df_pareto[f'{nivel}_similaridad_cos'] * df_pareto['peso'] | |
| resultado = df_pareto.groupby(f'{nivel.upper()}_ID').agg({ | |
| 'score': 'sum', | |
| 'INICIATIVA_ID': 'count' | |
| }).rename(columns={'INICIATIVA_ID': 'frecuencia'}) | |
| elif metodo == 'exponencial': | |
| df_pareto['peso'] = df_pareto[f'{nivel}_similaridad_cos_norm'] ** 2 | |
| resultado = df_pareto.groupby(f'{nivel.upper()}_ID').agg({ | |
| 'peso': 'sum', | |
| 'INICIATIVA_ID': 'count' | |
| }).rename(columns={'INICIATIVA_ID': 'frecuencia', 'peso': 'score'}) | |
| elif metodo == 'mixto': | |
| stats = df_pareto.groupby(f'{nivel.upper()}_ID').agg({ | |
| f'{nivel}_similaridad_cos_norm': 'mean', | |
| 'pareto_rank': 'mean', | |
| 'INICIATIVA_ID': 'count' | |
| }) | |
| # Normalizar componentes | |
| stats['sim_norm'] = stats[f'{nivel}_similaridad_cos_norm'] | |
| stats['rank_norm'] = 1 - (stats['pareto_rank'] - stats['pareto_rank'].min()) / (stats['pareto_rank'].max() - stats['pareto_rank'].min()) | |
| stats['freq_norm'] = stats['INICIATIVA_ID'] / stats['INICIATIVA_ID'].max() | |
| # Score mixto | |
| stats['score'] = ( | |
| sim_prop * stats['sim_norm'] + | |
| rank_prop * stats['rank_norm'] + | |
| (1 - sim_prop - rank_prop) * stats['freq_norm'] | |
| ) | |
| resultado = stats[['score', 'INICIATIVA_ID']].reset_index() | |
| resultado = resultado.sort_values('score', ascending=False).reset_index().rename(columns={'INICIATIVA_ID': 'frecuencia', | |
| 'index': 'rank'}) | |
| resultado['rank'] = resultado.index + 1 | |
| # Crear columna ODS_ID de forma robusta para todos los niveles | |
| if nivel == 'ods': | |
| # Para ODS, el ID ya es el ODS_ID | |
| resultado['ODS_ID'] = resultado['ODS_ID'].astype(str) | |
| elif nivel == 'meta': | |
| # Extraer ODS del META_ID (formato: "1.1", tomar el primer dígito) | |
| def extraer_ods_de_meta(meta_id): | |
| try: | |
| if pd.isna(meta_id): | |
| return None | |
| ods_num = str(meta_id).split('.')[0].strip() | |
| return ods_num if ods_num else None | |
| except: | |
| return None | |
| resultado['ODS_ID'] = resultado['META_ID'].apply(extraer_ods_de_meta) | |
| elif nivel == 'indicador': | |
| # Extraer ODS del INDICADOR_ID (formato: "1.1.1", tomar el primer dígito) | |
| def extraer_ods_de_indicador(ind_id): | |
| try: | |
| if pd.isna(ind_id): | |
| return None | |
| ods_num = str(ind_id).split('.')[0].strip() | |
| return ods_num if ods_num else None | |
| except: | |
| return None | |
| resultado['ODS_ID'] = resultado['INDICADOR_ID'].apply(extraer_ods_de_indicador) | |
| return gr.update(value=resultado), gr.update(value=df_pareto) | |
| except Exception as e: | |
| print(f"Error en analisis_global_con_pareto: {str(e)}") | |
| import traceback | |
| traceback.print_exc() | |
| return gr.update(value=pd.DataFrame()), gr.update(value=pd.DataFrame()) | |
| # ============================================================================ | |
| # FUNCIONES PARA CADA PESTAÑA | |
| # ============================================================================ | |
| def tab_inicio(df_ods, df_metas, df_indicador): | |
| # def tab_inicio(): | |
| """Pestaña de inicio con resumen general""" | |
| if not DATOS_CARGADOS: | |
| return "⚠️ Error: No se pudieron cargar los datos." | |
| # Estadísticas básicas | |
| total_ods = df_ods['ODS_ID'].nunique() | |
| total_metas = df_metas['META_ID'].nunique() | |
| total_indicadores = df_indicador['INDICADOR_ID'].nunique() | |
| sim_media = df_ods['ods_similaridad_cos_normalized'].mean() | |
| sim_max = df_ods['ods_similaridad_cos_normalized'].max() | |
| sim_min = df_ods['ods_similaridad_cos_normalized'].min() | |
| correlacion = df_ods['ods_rank'].corr(df_ods['ods_similaridad_cos_normalized']) | |
| # Top 4 ODS | |
| top_ods = df_ods.nsmallest(4, 'ods_rank')[['ODS_ID','ods_rank','OBJETIVO','ods_similaridad_cos_normalized']] | |
| top_ods['logo_id'] = top_ods['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| # top_ods = df_ods.groupby('ODS_ID').agg({ | |
| # 'ods_similaridad_cos_normalized': 'mean' | |
| # }).sort_values('ods_similaridad_cos_normalized', ascending=False).head(3)[['ods_similaridad_cos_normalized']] | |
| # Top ODS referencia | |
| ods_ref = top_ods.ODS_ID | |
| # Top 3 METAS | |
| top_metas = pd.DataFrame() | |
| for i in ods_ref: | |
| top_metas_lcl = df_metas[df_metas.ODS_ID == i] | |
| top_metas_lcl = top_metas_lcl.nsmallest(2, 'meta_rank')[['META_ID','meta_rank','META','meta_similaridad_cos_normalized', 'ODS_ID']] | |
| top_metas = pd.concat([top_metas, top_metas_lcl], axis=0) | |
| # top_metas['logo_id'] = top_metas['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| top_metas['logo_id'] = top_metas['META_ID'].apply(lambda _: f"meta_{_.upper()}") | |
| recomendaciones_tblinput = Path('data/raw/ODS_169_metas_recomendaciones_detalladas.xlsx') | |
| df_recomendaciones = pd.read_excel(recomendaciones_tblinput) | |
| top_metas = top_metas.merge(df_recomendaciones[['Meta_ODS', 'Recomendaciones_territoriales']], left_on='META_ID', right_on='Meta_ODS', how='left') | |
| # top_metas = df_metas.groupby('META_ID').agg({ | |
| # 'meta_similaridad_cos_normalized': 'mean' | |
| # }).sort_values('meta_similaridad_cos_normalized', ascending=False).head(5)[['META_ID','META','meta_similaridad_cos_normalized']] | |
| # Top 5 indicadores | |
| top_indicador = pd.DataFrame() | |
| for i in ods_ref: | |
| top_indicador_lcl = df_indicador[df_indicador.ODS_ID == i] | |
| top_indicador_lcl = top_indicador_lcl.nsmallest(2, 'indicador_rank')[['INDICADOR_ID', 'indicador_rank', 'INDICADOR', 'indicador_similaridad_cos_normalized', 'ODS_ID']] | |
| top_indicador = pd.concat([top_indicador, top_indicador_lcl], axis=0) | |
| top_indicador['logo_id'] = top_indicador['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| html = f""" | |
| <div style="font-family: Arial, sans-serif; padding: 20px;"> | |
| <h1 style="color: #2E5090; text-align: center;"> | |
| 📊 Tu texto en clave de ODS | |
| </h1> | |
| <h2 style="color: #4472C4; text-align: center;"> | |
| El análisis | |
| identifica que tu texto se relaciona principalmente | |
| con estos Objetivos de Desarrollo Sostenible ODS | |
| </h2> | |
| <!-- | |
| <div class="stats-box" style="background-color: #E8F4F8; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #2E5090;">📈 Estadísticas Generales</h3> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Total de indicadores analizados:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{total_indicadores}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>ODS cubiertos:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{total_ods}/17 (100%)</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Similaridad promedio:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{sim_media:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Rango de similaridad:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{sim_min:.4f} - {sim_max:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Correlación Rank-Similaridad:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right; color: {'green' if correlacion < -0.7 else 'orange'};"> | |
| {correlacion:.4f} {'✅' if correlacion < -0.7 else '⚠️'} | |
| </td> | |
| </tr> | |
| </table> | |
| </div> | |
| --> | |
| <div class="important-box" style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #E6F7E6;"> | |
| <h3 style="color: #E6F7E6;">🏆 Top 4 ODS Más Relevantes</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(400*(row['ods_similaridad_cos_normalized']**3))}" | |
| style="margin: 0 auto; display: block;" /> | |
| <!-- <p style="margin: 10px 0; color: #333; font-weight: bold;">{row['OBJETIVO']}</p> --> | |
| </div>''' for _, row in top_ods.iterrows()])} | |
| </div> | |
| </div> | |
| <div class="important-box" style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #FFD700;"> | |
| <h3 style="color: #009EDB;">🎯 Metas Más Relevantes Por ODS Top</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(200*(row['meta_similaridad_cos_normalized']**3))}" | |
| style="margin: 0 auto; display: block;" /> | |
| <p style="margin: 10px 0 5px 0; color: #333; font-size: 12px;"><strong style="color: #333;">Meta:</strong> {row['META_ID']}</p> | |
| <!-- <p style="margin: 0; color: #666; font-size: 12px;">{row['META']}</p> --> | |
| <!-- <p style="margin: 5px 0 0 0; color: #999; font-size: 11px;">Sim: {row['meta_similaridad_cos_normalized']:.3f}</p> --> | |
| <!-- <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Rank: {row['meta_rank']}</p> --> | |
| <!-- <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Rank: {row['Recomendaciones_territoriales']}</p> --> | |
| </div>''' for _, row in top_metas.iterrows()])} | |
| </div> | |
| </div> | |
| <!-- | |
| <div class="important-box" style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #FFD700;"> | |
| <h3 style="color: #FF8C00;">🎯 Indicadores Más Relevantes Por ODS Top</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(200*(row['indicador_similaridad_cos_normalized']**3))}" | |
| style="margin: 0 auto; display: block;" /> | |
| <p style="margin: 10px 0 5px 0; color: #333; font-size: 12px;"><strong style="color: #333;">Ind:</strong> {row['INDICADOR_ID']}</p> | |
| <p style="margin: 0; color: #666; font-size: 12px;">{row['INDICADOR']}</p> | |
| <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Sim: {row['indicador_similaridad_cos_normalized']:.3f}</p> | |
| <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Rank: {row['indicador_rank']}</p> | |
| </div>''' for _, row in top_indicador.iterrows()])} | |
| </div> | |
| </div> | |
| --> | |
| <div class="important-box" style="background-color: #E8F4F8; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #4472C4;"> | |
| <h3 style="color: #2E5090;">📋 Recomendaciones Territoriales por Meta</h3> | |
| <table style="width: 100%; border-collapse: collapse; background-color: white;"> | |
| <thead> | |
| <tr style="background-color: #2E5090; color: white;"> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090;">ODS</th> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090;">Meta ID</th> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090;">Recomendaciones Territoriales</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {''.join([f''' | |
| <tr style="border-bottom: 1px solid #ddd;"> | |
| <td style="padding: 12px; text-align: center;"> | |
| <img src="{dict_logos[f'ods_{row['ODS_ID']}']}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="50" | |
| style="vertical-align: middle;" /> | |
| </td> | |
| <td style="padding: 12px; font-weight: bold; color: #2E5090;">{row['META_ID']}</td> | |
| <td style="padding: 12px; color: #333;">{row['Recomendaciones_territoriales']}</td> | |
| </tr>''' for _, row in top_metas.iterrows()])} | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- | |
| <div style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #2E5090;">📚 Cómo usar esta aplicación</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>Explora las pestañas:</strong> Cada pestaña contiene una visualización diferente</li> | |
| <li><strong>Lee las explicaciones:</strong> Cada gráfica incluye una guía de interpretación</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones HTML permiten zoom, hover y exploración</li> | |
| <li><strong>Descarga:</strong> Puedes descargar las imágenes desde las pestañas</li> | |
| </ol> | |
| </div> | |
| <div style="text-align: center; margin-top: 30px; padding: 20px; background-color: #E8F4F8; border-radius: 10px;"> | |
| <p style="font-size: 18px; color: #2E5090;"> | |
| <strong>¡Comienza explorando las visualizaciones en las pestañas superiores!</strong> | |
| </p> | |
| <p style="color: #666;"> | |
| Recomendación: Empieza con el "Dashboard Integrado" para una vista general | |
| </p> | |
| </div> | |
| --> | |
| </div> | |
| """ | |
| return html | |
| def tab_inicio_prompt(df_ods, df_metas, df_indicador): | |
| # def tab_inicio(): | |
| """Pestaña de inicio con resumen general""" | |
| if not DATOS_CARGADOS: | |
| return "⚠️ Error: No se pudieron cargar los datos." | |
| # Estadísticas básicas | |
| # total_ods = df_ods['ODS_ID'].nunique() | |
| # total_metas = df_metas['META_ID'].nunique() | |
| # total_indicadores = df_indicador['INDICADOR_ID'].nunique() | |
| # sim_media = df_ods['ods_similaridad_cos_normalized'].mean() | |
| # sim_max = df_ods['ods_similaridad_cos_normalized'].max() | |
| # sim_min = df_ods['ods_similaridad_cos_normalized'].min() | |
| # correlacion = df_ods['ods_rank'].corr(df_ods['ods_similaridad_cos_normalized']) | |
| # Top 4 ODS | |
| top_ods = df_ods.nsmallest(4, 'rank')[['rank', 'ODS_ID', 'score', 'frecuencia']] | |
| top_ods['logo_id'] = top_ods['ODS_ID'].apply(lambda _: f"ods_{str(_)}") | |
| # top_ods = df_ods.groupby('ODS_ID').agg({ | |
| # 'ods_similaridad_cos_normalized': 'mean' | |
| # }).sort_values('ods_similaridad_cos_normalized', ascending=False).head(3)[['ods_similaridad_cos_normalized']] | |
| # Top ODS referencia | |
| ods_ref = top_ods.ODS_ID | |
| # Top 3 METAS | |
| top_metas = df_metas.nsmallest(8, 'rank')[['rank', 'META_ID', 'score', 'frecuencia']] | |
| # top_metas = df_metas[['rank', 'META_ID', 'score', 'frecuencia']] | |
| # for i in ods_ref: | |
| # top_metas_lcl = df_metas[df_metas.ODS_ID == i] | |
| # top_metas_lcl = top_metas_lcl.nsmallest(2, 'meta_rank')[['META_ID','meta_rank','META','meta_similaridad_cos_normalized', 'ODS_ID']] | |
| # top_metas = pd.concat([top_metas, top_metas_lcl], axis=0) | |
| # top_metas['logo_id'] = top_metas['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| top_metas['logo_id'] = top_metas['META_ID'].apply(lambda _: f"meta_{str(_).upper()}") | |
| recomendaciones_tblinput = Path('data/raw/ODS_169_metas_recomendaciones_detalladas.xlsx') | |
| df_recomendaciones = pd.read_excel(recomendaciones_tblinput) | |
| top_metas = top_metas.merge(df_recomendaciones[['Meta_ODS', 'Recomendaciones_territoriales']], left_on='META_ID', right_on='Meta_ODS', how='left') | |
| # top_metas = df_metas.groupby('META_ID').agg({ | |
| # 'meta_similaridad_cos_normalized': 'mean' | |
| # }).sort_values('meta_similaridad_cos_normalized', ascending=False).head(5)[['META_ID','META','meta_similaridad_cos_normalized']] | |
| # Top 5 indicadores | |
| # top_indicador = df_metas.nsmallest(4, 'ods_rank')[['rank', 'INDICADOR_ID', 'score', 'frecuencia']] | |
| # top_indicador['ODS_ID'] = top_indicador['INDICADOR_ID'].apply(lambda x: str(x).split('.')[0] if pd.notna(x) else None) | |
| # top_indicador['logo_id'] = top_indicador['ODS_ID'].apply(lambda _: f"ods_{str(_)}") | |
| html = f""" | |
| <div style="font-family: Arial, sans-serif; padding: 20px;"> | |
| <h1 style="color: #2E5090; text-align: center;"> | |
| 📊 Tu texto en clave de ODS | |
| </h1> | |
| <h2 style="color: #4472C4; text-align: center;"> | |
| El análisis | |
| identifica que tu texto se relaciona principalmente | |
| con estos Objetivos de Desarrollo Sostenible ODS | |
| </h2> | |
| <div class="important-box" style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #E6F7E6;"> | |
| <h3 style="color: #E6F7E6;">🏆 Top 4 ODS Más Relevantes</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(400*(row['score']))}" | |
| style="margin: 0 auto; display: block;" /> | |
| </div>''' for _, row in top_ods.iterrows()])} | |
| </div> | |
| </div> | |
| <div class="important-box" style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #FFD700;"> | |
| <h3 style="color: #009EDB;">🎯 Metas Más Relevantes Por ODS Top</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['META_ID']}" | |
| width="{int(200*(row['score']))}" | |
| style="vertical-align: bottom; display: block;" /> | |
| <p style="margin: 10px 0 5px 0; color: #333; font-size: 12px;"><strong style="color: #333;">Meta:</strong> {row['META_ID']}</p> | |
| </div>''' for _, row in top_metas.iterrows()])} | |
| </div> | |
| </div> | |
| <!-- | |
| <div class="important-box" style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #FFD700;"> | |
| <h3 style="color: #FF8C00;">🎯 Indicadores Más Relevantes Por ODS Top</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['META_ID']}" | |
| width="{int(200*(row['score']))}" | |
| style="margin: 0 auto; display: block;" /> | |
| <p style="margin: 10px 0 5px 0; color: #333; font-size: 12px;"><strong style="color: #333;">Ind:</strong> {row['META_ID']}</p> | |
| <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Rank: {row['rank']}</p> | |
| </div>''' for _, row in top_metas.iterrows()])} | |
| </div> | |
| </div> | |
| --> | |
| <div class="important-box" style="background-color: #E8F4F8; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #4472C4;"> | |
| <h3 style="color: #2E5090;">📋 Acciones Territoriales asociadas a Metas ODS</h3> | |
| <table style="width: 100%; border-collapse: collapse; background-color: white;"> | |
| <thead> | |
| <tr style="background-color: #2E5090; color: white;"> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090; color: white;">META</th> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090; color: white;">Meta ID</th> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090; color: white;">Desde las metas de ODS, ¿Cuáles son las acciones asociadas con la iniciativa, problemática u oportunidad en tu territorio?</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {''.join([f''' | |
| <tr style="border-bottom: 1px solid #ddd;"> | |
| <td style="padding: 12px; text-align: center;"> | |
| <img src="{dict_logos[f'meta_{row['META_ID']}']}" | |
| alt="ODS {row['META_ID']}" | |
| width="50" | |
| style="vertical-align: middle;" /> | |
| </td> | |
| <td style="padding: 12px; font-weight: bold; color: #2E5090;">{row['META_ID']}</td> | |
| <td style="padding: 12px; color: #333;">{row['Recomendaciones_territoriales']}</td> | |
| </tr>''' for _, row in top_metas.iterrows()])} | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- | |
| <div style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #2E5090;">📚 Cómo usar esta aplicación</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>Explora las pestañas:</strong> Cada pestaña contiene una visualización diferente</li> | |
| <li><strong>Lee las explicaciones:</strong> Cada gráfica incluye una guía de interpretación</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones HTML permiten zoom, hover y exploración</li> | |
| <li><strong>Descarga:</strong> Puedes descargar las imágenes desde las pestañas</li> | |
| </ol> | |
| </div> | |
| <div style="text-align: center; margin-top: 30px; padding: 20px; background-color: #E8F4F8; border-radius: 10px;"> | |
| <p style="font-size: 18px; color: #2E5090;"> | |
| <strong>¡Comienza explorando las visualizaciones en las pestañas superiores!</strong> | |
| </p> | |
| <p style="color: #666;"> | |
| Recomendación: Empieza con el "Dashboard Integrado" para una vista general | |
| </p> | |
| </div> | |
| --> | |
| </div> | |
| """ | |
| return html | |
| def tab_inicio_mass(df_ods=None, df_metas=None, df_indicador=None): | |
| # def tab_inicio(): | |
| """Pestaña de inicio con resumen general""" | |
| # Validación de entrada | |
| if df_ods is None or df_ods.empty: | |
| return "<p>⚠️ No hay datos ODS disponibles</p>" | |
| if df_metas is None or df_metas.empty: | |
| return "<p>⚠️ No hay datos de METAS disponibles</p>" | |
| # if not DATOS_CARGADOS: | |
| # return "⚠️ Error: No se pudieron cargar los datos." | |
| # print('ODS columns:', df_ods.columns) | |
| # print('METAS columns:', df_metas.columns) | |
| # print('INDICADOR columns:', df_indicador.columns) | |
| # Estadísticas básicas | |
| # total_ods = df_ods['ODS_ID'].nunique() | |
| # total_metas = df_metas['META_ID'].nunique() | |
| # total_indicadores = df_indicador['INDICADOR_ID'].nunique() | |
| # sim_media = df_ods['score'].mean() | |
| # sim_max = df_ods['score'].max() | |
| # sim_min = df_ods['score'].min() | |
| # correlacion = df_ods['rank'].corr(df_ods['score']) | |
| # Top 4 ODS | |
| top_ods = df_ods.nsmallest(4, 'rank')[['ODS_ID','rank','score']] | |
| top_ods['logo_id'] = top_ods['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| # top_ods = df_ods.groupby('ODS_ID').agg({ | |
| # 'score': 'mean' | |
| # }).sort_values('score', ascending=False).head(3)[['score']] | |
| # Top ODS referencia | |
| ods_ref = top_ods.ODS_ID | |
| # Top 3 METAS | |
| # top_metas = pd.DataFrame() | |
| # for i in ods_ref: | |
| # top_metas_lcl = df_metas[df_metas['ODS_ID'] == i] | |
| # top_metas_lcl = top_metas_lcl.nsmallest(2, 'rank')[['META_ID','rank','score','ODS_ID']] | |
| # top_metas = pd.concat([top_metas, top_metas_lcl], axis=0) | |
| # # top_metas['logo_id'] = top_metas['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| top_metas = df_metas.nsmallest(8, 'rank')[['META_ID','rank','score','ODS_ID']] | |
| top_metas['logo_id'] = top_metas['META_ID'].apply(lambda _: f"meta_{_.upper()}") | |
| # top_metas = df_metas.groupby('META_ID').agg({ | |
| # 'meta_similaridad_cos_normalized': 'mean' | |
| # }).sort_values('meta_similaridad_cos_normalized', ascending=False).head(5)[['META_ID','META','meta_similaridad_cos_normalized']] | |
| recomendaciones_tblinput = Path('data/raw/ODS_169_metas_recomendaciones_detalladas.xlsx') | |
| df_recomendaciones = pd.read_excel(recomendaciones_tblinput) | |
| top_metas = top_metas.merge(df_recomendaciones[['Meta_ODS', 'Recomendaciones_territoriales']], left_on='META_ID', right_on='Meta_ODS', how='left') | |
| # Top 5 indicadores | |
| top_indicador = pd.DataFrame() | |
| if df_indicador is not None and not df_indicador.empty and 'ODS_ID' in df_indicador.columns: | |
| for i in ods_ref: | |
| top_indicador_lcl = df_indicador[df_indicador['ODS_ID'] == i] | |
| if not top_indicador_lcl.empty: | |
| # Verificar qué columnas existen en el DataFrame | |
| cols_disponibles = [col for col in ['INDICADOR_ID', 'rank', 'score', 'ODS_ID'] if col in top_indicador_lcl.columns] | |
| top_indicador_lcl = top_indicador_lcl.nsmallest(2, 'rank')[cols_disponibles] if 'rank' in cols_disponibles else top_indicador_lcl[cols_disponibles].head(2) | |
| top_indicador = pd.concat([top_indicador, top_indicador_lcl], axis=0) | |
| if not top_indicador.empty: | |
| top_indicador['logo_id'] = top_indicador['ODS_ID'].apply(lambda _: f"ods_{_}") | |
| else: | |
| top_indicador = pd.DataFrame() | |
| html = f""" | |
| <div style="font-family: Arial, sans-serif; padding: 20px;"> | |
| <h1 style="color: #2E5090; text-align: center;"> | |
| 📊 Tus textos en clave de ODS | |
| </h1> | |
| <h2 style="color: #4472C4; text-align: center;"> | |
| El análisis | |
| identifica que tus textos se relacionan principalmente | |
| con estos Objetivos de Desarrollo Sostenible ODS | |
| </h2> | |
| <div class="important-box" style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #E6F7E6;"> | |
| <h3 style="color: #E6F7E6;">🏆 Top 4 ODS Más Relevantes</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(400*(row['score']**3))}" | |
| style="margin: 0 auto; display: block;" /> | |
| </div>''' for _, row in top_ods.iterrows()])} | |
| </div> | |
| </div> | |
| <div class="important-box" style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #FFD700;"> | |
| <h3 style="color: #333;">🎯 Metas Más Relevantes Por ODS Top</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(200*(row['score']**1))}" | |
| style="margin: 0 auto; display: block;" /> | |
| <p style="margin: 10px 0 5px 0; color: #333; font-size: 12px;"><strong color="#333">Meta:</strong> {row['META_ID']}</p> | |
| <!-- <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Sim: {row['score']:.3f}</p> --> | |
| <!-- <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Rank: {row['rank']}</p> --> | |
| <!-- <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Recomendación: {row['Recomendaciones_territoriales']}</p> --> | |
| </div>''' for _, row in top_metas.iterrows()])} | |
| </div> | |
| </div> | |
| <!-- | |
| <div class="important-box" style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #FFD700;"> | |
| <h3 style="color: #FF8C00;">🎯 Indicadores Más Relevantes Por ODS Top</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: space-around; align-items: flex-start;"> | |
| {''.join([f''' | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="{int(100*(row['score']**3))}" | |
| style="margin: 0 auto; display: block;" /> | |
| <p style="margin: 10px 0 5px 0; color: #333; font-size: 12px;"><strong color="#333">Ind:</strong> {row['INDICADOR_ID']}</p> | |
| <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Sim: {row['score']:.3f}</p> | |
| <p style="margin: 5px 0 0 0; color: #333; font-size: 11px;">Rank: {row['rank']}</p> | |
| </div>''' for _, row in top_indicador.iterrows()])} | |
| </div> | |
| </div> | |
| --> | |
| <div class="important-box" style="background-color: #E8F4F8; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #4472C4;"> | |
| <h3 style="color: #2E5090;">📋 Acciones Territoriales asociadas a Metas ODS</h3> | |
| <table style="width: 100%; border-collapse: collapse; background-color: white;"> | |
| <thead> | |
| <tr style="background-color: #2E5090; color: white;"> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090; color: white;">ODS</th> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090; color: white;">Meta ID</th> | |
| <th style="padding: 12px; text-align: left; border-bottom: 2px solid #2E5090; color: white;">Desde las metas de ODS, ¿Cuáles son las acciones asociadas con la iniciativa, problemática u oportunidad en tu territorio?</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {''.join([f''' | |
| <tr style="border-bottom: 1px solid #ddd;"> | |
| <td style="padding: 12px; text-align: center;"> | |
| <img src="{dict_logos[row['logo_id']]}" | |
| alt="ODS {row['ODS_ID']}" | |
| width="50" | |
| style="vertical-align: middle;" /> | |
| </td> | |
| <td style="padding: 12px; font-weight: bold; color: #2E5090;">{row['META_ID']}</td> | |
| <td style="padding: 12px; color: #333;">{row['Recomendaciones_territoriales']}</td> | |
| </tr>''' for _, row in top_metas.iterrows()])} | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- | |
| <div style="background-color: #F0F0F0; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #2E5090;">📚 Cómo usar esta aplicación</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>Explora las pestañas:</strong> Cada pestaña contiene una visualización diferente</li> | |
| <li><strong>Lee las explicaciones:</strong> Cada gráfica incluye una guía de interpretación</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones HTML permiten zoom, hover y exploración</li> | |
| <li><strong>Descarga:</strong> Puedes descargar las imágenes desde las pestañas</li> | |
| </ol> | |
| </div> | |
| <div style="text-align: center; margin-top: 30px; padding: 20px; background-color: #E8F4F8; border-radius: 10px;"> | |
| <p style="font-size: 18px; color: #2E5090;"> | |
| <strong>¡Comienza explorando las visualizaciones en las pestañas superiores!</strong> | |
| </p> | |
| <p style="color: #666;"> | |
| Recomendación: Empieza con el "Dashboard Integrado" para una vista general | |
| </p> | |
| </div> | |
| --> | |
| </div> | |
| """ | |
| return html | |
| def tab_viz1(df_ods, df_metas, df_indicador): | |
| # def tab_viz1(): | |
| """Visualización 1: Box Plot por ODS""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig1 = viz_1_distribucion_por_ods(df_ods, 'ODS_ID', 'ods_similaridad_cos_normalized', 'ODS') | |
| fig2 = viz_1_distribucion_por_ods(df_metas, 'META_ID', 'meta_similaridad_cos_normalized', 'META') | |
| fig3 = viz_1_distribucion_por_ods(df_indicador, 'INDICADOR_ID', 'indicador_similaridad_cos_normalized', 'INDICADOR') | |
| explicacion = """ | |
| ## 📦 Diagrama de Caja por ODS | |
| ### ¿Qué muestra? | |
| Esta visualización muestra cómo se distribuyen los valores de similaridad para cada uno de los 17 ODS. | |
| ### ¿Cómo leerlo? | |
| - **Línea central**: Mediana (valor del medio) | |
| - **Caja**: Rango intercuartílico (Q1 a Q3) | |
| - **Líneas extendidas**: Valores mínimos y máximos normales | |
| - **Puntos fuera**: Valores atípicos (outliers) | |
| ### Interpretación: | |
| - ✅ **Cajas altas**: Mucha variación entre indicadores del ODS | |
| - ✅ **Cajas pequeñas**: Indicadores consistentes | |
| - ✅ **Mediana alta**: ODS muy relacionado con la iniciativa | |
| - ✅ **Puntos aislados**: Indicadores especialmente relevantes | |
| ### 💡 Consejo: | |
| Busca ODS con medianas altas y cajas pequeñas para identificar objetivos con indicadores consistentemente relevantes. | |
| """ | |
| return fig1, fig2, fig3, explicacion | |
| def tab_viz2(df_global): | |
| # def tab_viz2(): | |
| """Visualización 2: Heatmap ODS × Ranking""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_2_heatmap_ods_ranking(df_global) | |
| filepath = matplotlib_to_file(fig, 'viz2_heatmap.png') | |
| explicacion = """ | |
| ## 🔥 Mapa de Calor: ODS × Ranking | |
| ### ¿Qué muestra? | |
| Matriz bidimensional que cruza los 17 ODS (filas) con deciles de ranking (columnas), | |
| mostrando la similaridad promedio en cada celda. | |
| ### ¿Cómo leerlo? | |
| - 🔴 **Colores cálidos** (rojo/naranja): Alta similaridad | |
| - 🔵 **Colores fríos** (verde/azul): Baja similaridad | |
| - **D1 a D10**: Desde los más relevantes (D1) hasta los menos (D10) | |
| ### Interpretación: | |
| - ✅ **Fila roja completa**: ODS relevante en todos los rangos | |
| - ✅ **Columna roja**: Varios ODS relevantes en esa posición | |
| - ✅ **Diagonal descendente**: Patrón esperado (a mayor rank, menor similaridad) | |
| - ✅ **Rojo en D1-D2**: Los ODS más críticos | |
| ### 💡 Consejo: | |
| Identifica rápidamente qué ODS dominan en las posiciones altas del ranking. | |
| """ | |
| return filepath, explicacion | |
| def tab_viz3(df_global): | |
| # def tab_viz3(): | |
| """Visualización 3: Scatter 3D Interactivo""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_3_scatter_3d_interactivo(df_global) | |
| explicacion = """ | |
| ## 🌐 Gráfico 3D Interactivo | |
| ### ¿Qué muestra? | |
| Visualización tridimensional donde cada punto representa un indicador. | |
| ### Las tres dimensiones: | |
| - **Eje X**: ODS ID (1-17) | |
| - **Eje Y**: Número de sub-indicador | |
| - **Eje Z**: Similaridad (altura del punto) | |
| - **Tamaño**: Los más grandes = más relevantes | |
| - **Color**: Cada ODS tiene su color | |
| ### Interactividad: | |
| - 🔄 **Rotar**: Arrastra con el mouse | |
| - 🔍 **Zoom**: Scroll o pinch | |
| - 👆 **Hover**: Pasa el mouse sobre puntos | |
| ### Interpretación: | |
| - ✅ **Puntos altos**: Alta similaridad | |
| - ✅ **Clusters de color**: Grupo de indicadores relacionados | |
| - ✅ **Puntos grandes y altos**: Los más importantes | |
| ### 💡 Consejo: | |
| Rota el gráfico para descubrir patrones ocultos y agrupaciones de indicadores. | |
| """ | |
| return fig, explicacion | |
| def tab_viz4(df, id_lvl, score, rank, titulo): | |
| # def tab_viz4(): | |
| """Visualización 4: Radar Chart""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_4_radar_chart_ods(df, id_lvl, score, rank, titulo) | |
| explicacion = """ | |
| ## 🕸️ Gráfico de Radar (Perfil ODS) | |
| ### ¿Qué muestra? | |
| Gráfico circular que muestra el 'perfil ODS' de tu iniciativa con dos métricas. | |
| ### Cómo leerlo: | |
| - 🔵 **Polígono azul**: Similaridad promedio por ODS | |
| - 🔴 **Polígono rojo**: Similaridad máxima (mejor indicador) | |
| - **Distancia del centro**: Mayor distancia = mayor similaridad | |
| ### Interpretación: | |
| - ✅ **Picos hacia afuera**: ODS muy relevantes | |
| - ✅ **Valles hacia dentro**: ODS menos relacionados | |
| - ✅ **Forma circular**: Iniciativa equilibrada | |
| - ✅ **Forma irregular**: Especialización en ODS específicos | |
| - ✅ **Gap azul-rojo grande**: Indicador estrella en ese ODS | |
| ### 💡 Consejo: | |
| Ideal para presentaciones ejecutivas. Muestra de un vistazo el perfil completo de alineación ODS. | |
| """ | |
| return fig, explicacion | |
| def tab_viz5(df, id_lvl, score, rank, titulo): | |
| # def tab_viz5(): | |
| """Visualización 5: Sunburst""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_5_sunburst_jerarquia(df, id_lvl, score, rank, titulo) | |
| explicacion = """ | |
| ## ☀️ Diagrama de Sol (Sunburst) | |
| ### ¿Qué muestra? | |
| Diagrama circular jerárquico mostrando ODS (centro) → Indicadores (anillo exterior). | |
| ### Cómo leerlo: | |
| - **Tamaño del segmento**: Proporcional a la similaridad | |
| - **Color**: Gradiente (más oscuro = mayor similaridad) | |
| - **Nivel 1 (centro)**: Los 17 ODS | |
| - **Nivel 2 (exterior)**: Indicadores individuales | |
| ### Interactividad: | |
| - 👆 **Click**: Zoom en un ODS específico | |
| - 🔍 **Hover**: Ver código y valor del indicador | |
| ### Interpretación: | |
| - ✅ **Segmentos grandes**: Indicadores muy relevantes | |
| - ✅ **ODS ocupa mucho espacio**: Muchos indicadores relevantes | |
| - ✅ **Colores oscuros**: Alta similaridad | |
| ### 💡 Consejo: | |
| Excelente para visualizar la contribución relativa de cada indicador al total. | |
| """ | |
| return fig, explicacion | |
| def tab_viz6(df, id_lvl, score, rank, titulo, top_n=3): | |
| # def tab_viz6(): | |
| """Visualización 6: Top Indicadores por ODS""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_6_top_indicadores_por_ods(df, id_lvl, score, rank, titulo, top_n=3) | |
| explicacion = """ | |
| ## 🏆 Top 5 Indicadores por ODS | |
| ### ¿Qué muestra? | |
| Barras horizontales con los 5 indicadores más relevantes de cada ODS. | |
| ### Cómo leerlo: | |
| - **Longitud de barra**: Valor de similaridad | |
| - **Primera barra**: El indicador más relevante | |
| - **Color**: Gradiente por similaridad | |
| - **Cada panel**: Un ODS diferente | |
| ### Interpretación: | |
| - ✅ **Barra mucho más larga**: Indicador campeón | |
| - ✅ **Barras parejas**: Varios indicadores igualmente relevantes | |
| - ✅ **Comparación entre ODS**: Qué objetivo tiene mejores indicadores | |
| ### 💡 Consejo: | |
| Perfecta para planificación estratégica. Te dice exactamente en qué indicadores enfocarte por cada ODS. | |
| """ | |
| return fig, explicacion | |
| def tab_viz7(df_global): | |
| # def tab_viz7(): | |
| """Visualización 7: Stream Graph""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_7_streamgraph_similaridad(df_global) | |
| explicacion = """ | |
| ## 🌊 Gráfico de Flujo (Stream Graph) | |
| ### ¿Qué muestra? | |
| Áreas apiladas que muestran cómo cambia la contribución porcentual de cada ODS | |
| a lo largo del ranking. | |
| ### Cómo leerlo: | |
| - **Eje horizontal**: Ranking agrupado (izq. = más relevante) | |
| - **Eje vertical**: Porcentaje de contribución (suma 100%) | |
| - **Ancho del color**: Porcentaje del ODS en ese rango | |
| ### Interpretación: | |
| - ✅ **Color dominante izquierda**: ODS líder en indicadores relevantes | |
| - ✅ **Cambio de color**: Transición de relevancia | |
| - ✅ **Área ancha constante**: ODS presente en todo el ranking | |
| - ✅ **Área que crece/decrece**: ODS relevante en ciertos rangos | |
| ### 💡 Consejo: | |
| Si un ODS ocupa mucho espacio a la izquierda, domina entre los indicadores más relevantes. | |
| """ | |
| return fig, explicacion | |
| def tab_viz8(df_global): | |
| # def tab_viz8(): | |
| """Visualización 8: Violin Plot""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_8_violin_plot_ods(df_global) | |
| explicacion = """ | |
| ## 🎻 Gráfico de Violín | |
| ### ¿Qué muestra? | |
| Similar al diagrama de caja pero con más detalle. Muestra la 'forma' completa | |
| de la distribución de similaridad por ODS. | |
| ### Cómo leerlo: | |
| - **Ancho del violín**: Concentración de valores | |
| - **Caja interior**: Mediana y cuartiles | |
| - **Línea horizontal**: Media (promedio) | |
| ### Concepto clave: | |
| El ancho representa la **densidad de probabilidad**: donde el violín es más ancho, | |
| es más probable encontrar indicadores con esos valores. | |
| ### Interpretación: | |
| - ✅ **Violín ancho en un punto**: Muchos indicadores similares | |
| - ✅ **Dos ensanchamientos**: Dos grupos distintos | |
| - ✅ **Violín delgado**: Pocos indicadores en ese rango | |
| - ✅ **Forma simétrica**: Distribución equilibrada | |
| ### 💡 Consejo: | |
| Detecta distribuciones complejas que el diagrama de caja no puede mostrar. | |
| """ | |
| return fig, explicacion | |
| def tab_viz9(df_global): | |
| # def tab_viz9(): | |
| """Visualización 9: Dashboard Integrado""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_9_dashboard_metricas(df_global) | |
| explicacion = """ | |
| ## 📊 Dashboard Integrado (4 Paneles) | |
| ### Panel 1 (Superior Izquierdo): Top 10 Indicadores | |
| Barras con los 10 indicadores más relevantes del análisis completo. | |
| ### Panel 2 (Superior Derecho): Estadísticas por ODS | |
| Tabla con media, desviación estándar, mínimo, máximo y cantidad por ODS. | |
| ### Panel 3 (Inferior Izquierdo): Histograma Global | |
| Distribución de frecuencias de todos los valores de similaridad. | |
| ### Panel 4 (Inferior Derecho): Correlación Rank-Similaridad | |
| Scatter plot con línea de tendencia. **CRÍTICO para validación del sistema**. | |
| ### Validación: | |
| - ✅ **Línea descendente**: Sistema funcionando correctamente | |
| - ✅ **Correlación < -0.7**: Excelente | |
| - ⚠️ **Correlación > -0.4**: Revisar sistema | |
| ### 💡 Consejo: | |
| Este debe ser tu punto de partida. Vista 360° del análisis completo. | |
| """ | |
| return fig, explicacion | |
| def tab_viz10(df_global): | |
| # def tab_viz10(): | |
| """Visualización 10: Matriz de Transición""" | |
| if not DATOS_CARGADOS: | |
| return None, "⚠️ Error: No se pudieron cargar los datos." | |
| fig = viz_10_matriz_transicion(df_global) | |
| filepath = matplotlib_to_file(fig, 'viz10_matriz_transicion.png') | |
| explicacion = """ | |
| ## 🔀 Matriz de Transición por Cuartiles | |
| ### ¿Qué muestra? | |
| Mapa de calor que muestra el porcentaje de cada ODS presente en los 4 cuartiles del ranking. | |
| ### Cómo leerlo: | |
| - **Filas**: Los 17 ODS | |
| - **Columnas**: Q1 (Top 25%), Q2, Q3, Q4 (Bottom 25%) | |
| - **Valores**: Porcentaje de presencia del ODS | |
| - **Colores**: Naranja/rojo = alta presencia | |
| ### Interpretación: | |
| - ✅ **Rojo intenso en Q1**: ODS crítico (domina rankings altos) | |
| - ✅ **Colores uniformes**: ODS consistente en todo el ranking | |
| - ✅ **Concentración en un cuartil**: ODS especializado | |
| - ✅ **Claro en Q1, oscuro en Q4**: Más relevante en posiciones bajas | |
| ### 💡 Consejo: | |
| Analiza la consistencia de relevancia por ODS. Alta presencia en Q1 = crítico para la iniciativa. | |
| """ | |
| return filepath, explicacion | |
| def tab_viz18(df, lvl, top): | |
| """Visualización 18: Pivot Table Interactivo""" | |
| # if not DATOS_RELACIONES_CARGADOS: | |
| # return None, "⚠️ Error: No se pudieron cargar los datos de relaciones." | |
| # Leer el HTML pre-generado | |
| # html_path = '/mnt/user-data/outputs/pivot_table_ods_interactivo.html' | |
| # if os.path.exists(html_path): | |
| # with open(html_path, 'r', encoding='utf-8') as f: | |
| # html_content = f.read() | |
| # else: | |
| # Si no existe, generarlo | |
| import tempfile | |
| from pivottablejs import pivot_ui | |
| # from viz_pivot_table import crear_pivot_table_html | |
| import uuid | |
| # Crear nombre único para evitar conflictos | |
| unique_id = str(uuid.uuid4())[:8] | |
| # Ruta temporal compatible con HF Spaces y local | |
| if os.path.exists('/tmp'): # Linux/HF Spaces | |
| temp_path = f'/tmp/pivot_table_{lvl}_{str(top).replace(".","")}.html' | |
| else: # Windows local | |
| temp_path = os.path.join(tempfile.gettempdir(), f'pivot_table_{lvl}_{str(top).replace(".","")}.html') | |
| pivot_ui( | |
| df, | |
| outfile_path=temp_path | |
| ) | |
| print(f"✓ Pivot Table generado: {temp_path}") | |
| with open(temp_path, 'r', encoding='utf-8') as f: | |
| html_content = f.read() | |
| explicacion = """ | |
| ## 🔄 Tabla Dinámica Interactiva | |
| Arrastra campos, cambia agregaciones y visualizaciones en tiempo real. | |
| """ | |
| return (gr.update(value=html_content), gr.update(value=explicacion), gr.update(value=temp_path)) | |
| def tab_viz19(df_ods_rel, df_metas_rel, df_indicadores_rel): | |
| """Visualización 19: Resumen en Tags""" | |
| # if not DATOS_RELACIONES_CARGADOS: | |
| # return "⚠️ Error: No se pudieron cargar los datos de relaciones.", "" | |
| html1, html2, html3 = viz_19_resumen_tags(df_ods_rel, df_metas_rel, df_indicadores_rel) | |
| explicacion = """ | |
| ## 📊 Resumen de Análisis en Tags | |
| ### ¿Qué muestra? | |
| Métricas clave del análisis presentadas en formato visual de tags/badges. | |
| ### Métricas incluidas: | |
| - **Iniciativas**: Total de iniciativas analizadas | |
| - **Promedios**: ODS, Metas e Indicadores promedio por iniciativa | |
| - **Más frecuentes**: Elementos que aparecen más veces en el análisis | |
| ### 💡 Interpretación: | |
| - **Promedios altos**: Iniciativas con enfoque amplio en múltiples ODS | |
| - **Elementos frecuentes**: ODS/Metas/Indicadores prioritarios en el conjunto | |
| """ | |
| return html1, html2, html3, explicacion | |
| def tab_viz20(df, nivel, nivel_pareto, mass = False ,iniciativa_id=None): | |
| """Visualización 20: Diagrama de Pareto""" | |
| # if not DATOS_RELACIONES_CARGADOS: | |
| # return None, "" | |
| # Seleccionar primera iniciativa | |
| # iniciativa_id = df['INICIATIVA_ID'].iloc[0] | |
| fig, corte_80 = viz_20_pareto_ods(df, nivel, nivel_pareto, mass, iniciativa_id) | |
| explicacion = f""" | |
| ## 📊 Diagrama de Pareto {nivel.upper()} Global | |
| ### ¿Qué muestra? | |
| Identifica los **{corte_80} {nivel.upper()} más críticos** que representan el **{nivel_pareto * 100}%** | |
| del valor total de aproximación semántica. | |
| ### Interpretación: | |
| - **Barras azules**: Aproximación semántica individual de cada {nivel.upper()} | |
| - **Línea roja**: Porcentaje acumulado | |
| - **Zona verde**: Top {corte_80} {nivel.upper()} críticos (regla de Pareto) | |
| - **Línea punteada**: Umbral {nivel_pareto * 100}% | |
| ### 💡 Uso: | |
| Estos **{corte_80} {nivel.upper()}** deberían ser tu enfoque prioritario, | |
| ya que concentran la mayor parte del valor. | |
| ### Principio de Pareto: | |
| El 80% de los resultados proviene del 20% de las causas. | |
| En este caso aplicamos un criterio de Pareto ADAPTATIVO: {corte_80} {nivel.upper()} generan el {nivel_pareto * 100}% de la aproximación semántica total. | |
| """ | |
| return fig, explicacion | |
| def tab_viz21(df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop=0.75, rank_prop=0.20): | |
| """Visualización 21: Ranking ODS Masivo""" | |
| # Validar entrada | |
| if df is None or df.empty: | |
| return None, "No hay datos para visualizar" | |
| try: | |
| resultado = df.copy() | |
| if nivel == 'meta': | |
| desc_metas = Path('data/raw/tabla_lvlMetaOds.xlsx') | |
| df_descmetas = pd.read_excel(desc_metas) | |
| df_descmetas['ID_META'] = df_descmetas['ID_META'].apply(lambda _: str(_).upper()) | |
| resultado = resultado.merge(df_descmetas[['ID_META', 'OBJETIVO', 'META']], left_on='META_ID', right_on='ID_META', how='left') | |
| elif nivel == 'ods': | |
| desc_metas = Path('data/raw/tabla_lvlMetaOds.xlsx') | |
| df_descmetas = pd.read_excel(desc_metas) | |
| df_descmetas['ID_OBJETIVO'] = df_descmetas['ID_OBJETIVO'].apply(lambda _: str(_).upper()) | |
| df_descmetas = df_descmetas[['ID_OBJETIVO', 'OBJETIVO']].drop_duplicates() | |
| resultado = resultado.merge(df_descmetas, left_on='ODS_ID', right_on='ID_OBJETIVO', how='left') | |
| fig = viz_21_ranking_mixto_masivo(resultado, nivel) | |
| explicacion = f"""## 🏆 Ranking Global de ODS - Análisis Masivo | |
| ### ¿Qué muestra? | |
| Resultado de aplicar **estrategia mixta ponderada** a iniciativas prioritarias. | |
| ### Componentes del Score: | |
| - Ranking promedio (posición) | |
| - Frecuencia de aparición | |
| ### Interpretación: | |
| - **Barras largas**: {nivel} con mayor score global | |
| - **Color verde**: Scores más altos | |
| - **Color azul**: Scores intermedios | |
| - **Círculos grandes**: {nivel} que aparecen en más iniciativas | |
| ### Top 3 ODS: | |
| {chr(10).join([f'- **{nivel} {row[f"{nivel.upper()}_ID"]}**: Score {row["score"]:.4f} ({int(row["frecuencia"])} iniciativas)' | |
| for _, row in resultado.head(3).iterrows()] if len(resultado) > 0 else ['Sin datos'])} | |
| ### 💡 Uso: | |
| Est@s {nivel} son los **prioritarios a nivel agregado** considerando | |
| relevancia, posición y prevalencia en el grupo de textos analizados. | |
| """ | |
| return fig, explicacion | |
| except Exception as e: | |
| print(f"Error en tab_viz21: {str(e)}") | |
| import traceback | |
| traceback.print_exc() | |
| return None, f"Error en visualización: {str(e)}" | |
| def tab_viz22(w_sim, w_rank): | |
| """Visualización 22: Composición Score Mixto""" | |
| # Pesos de la estrategia mixta | |
| # w_sim = 0.5 | |
| # w_rank = 0.3 | |
| # w_freq = 0.2 | |
| fig = viz_22_composicion_score_mixto(w_sim, w_rank, 1 - (w_sim + w_rank)) | |
| explicacion = f""" | |
| ## ⚖️ Composición del Score Mixto | |
| ### ¿Qué muestra? | |
| La distribución porcentual de los 3 componentes que conforman el score. | |
| ### Componentes: | |
| **1. Similaridad ({w_sim*100:.0f}%)** | |
| - Qué mide: Relevancia semántica promedio | |
| - Cálculo: Promedio de similaridad coseno normalizada | |
| - Peso: Mayor componente (calidad del match) | |
| **2. Ranking ({w_rank*100:.0f}%)** | |
| - Qué mide: Posición promedio en las iniciativas | |
| - Cálculo: Promedio de rankings invertido y normalizado | |
| - Peso: Segundo componente (importancia relativa) | |
| **3. Frecuencia ({(1 - (w_sim + w_rank))*100:.0f}%)** | |
| - Qué mide: Prevalencia en el lote | |
| - Cálculo: Cantidad de iniciativas donde aparece | |
| - Peso: Menor componente (consistencia) | |
| ### 📐 Fórmula: | |
| Score = {w_sim} × Similaridad + {w_rank} × Ranking + {1 - (w_sim + w_rank)} × Frecuencia | |
| ### 💡 Justificación de Pesos: | |
| - **Similaridad dominante**: La calidad del match es lo más importante | |
| - **Ranking secundario**: La posición importa, pero menos que la similaridad | |
| - **Frecuencia terciaria**: Aparecer en muchas iniciativas suma, pero no define | |
| ### 🔧 Personalización: | |
| Estos pesos pueden ajustarse según prioridades: | |
| - ¿Más importancia a frecuencia? → Aumentar w_freq a 0.3-0.4 | |
| - ¿Solo calidad del match? → Aumentar w_sim a 0.6-0.7 | |
| """ | |
| return fig, explicacion | |
| def tab_viz24(): | |
| """Visualización 24: Header ConectaODS""" | |
| # Buscar logo en diferentes ubicaciones posibles | |
| posibles_logos = [ | |
| '/mnt/user-data/uploads/logo_conecta_ods.png', | |
| 'logos/conecta_ods.png', | |
| 'assets/logo.png', | |
| None # Fallback a placeholder | |
| ] | |
| logo_path = None | |
| for ruta in posibles_logos: | |
| if ruta and os.path.exists(ruta): | |
| logo_path = ruta | |
| break | |
| html = viz_24_header_conecta_ods(logo_path) | |
| explicacion = """ | |
| ## 🎨 Header ConectaODS | |
| ### Componente de Presentación | |
| Este es el header principal de la aplicación que incluye: | |
| ✅ **Logo** de ConectaODS (o placeholder si no se carga) | |
| ✅ **Título** con versión | |
| ✅ **Eslogan** distintivo | |
| ✅ **Descripción** de la herramienta | |
| ✅ **Badges** informativos (17 ODS, 169 Metas, 244+ Indicadores) | |
| ✅ **Créditos** institucionales | |
| ### 💡 Personalización: | |
| Para usar tu propio logo: | |
| 1. Sube la imagen a `/mnt/user-data/uploads/logo_conecta_ods.png` | |
| 2. O colócala en `logos/conecta_ods.png` | |
| 3. Regenera la visualización | |
| El componente se adapta automáticamente con o sin logo. | |
| """ | |
| return html #, explicacion | |
| def tab_viz23(df_texto, columna, max_palabras=100): | |
| """Visualización 23: Nube de Palabras - Paloma de Paz""" | |
| # if not DATOS_RELACIONES_CARGADOS: | |
| # return None, "" | |
| # try: | |
| # # Seleccionar columna con más texto | |
| # # Probar primero con descripciones de ODS, luego metas, luego indicadores | |
| # if 'OBJETIVO' in df_ods_rel.columns and df_ods_rel['OBJETIVO'].notna().sum() > 0: | |
| # df_texto = df_ods_rel | |
| # columna = 'OBJETIVO' | |
| # tipo = 'Objetivos (ODS)' | |
| # elif 'META' in df_metas_rel.columns and df_metas_rel['META'].notna().sum() > 0: | |
| # df_texto = df_metas_rel | |
| # columna = 'META' | |
| # tipo = 'Metas' | |
| # elif 'INDICADOR' in df_indicadores_rel.columns and df_indicadores_rel['INDICADOR'].notna().sum() > 0: | |
| # df_texto = df_indicadores_rel | |
| # columna = 'INDICADOR' | |
| # tipo = 'Indicadores' | |
| # else: | |
| # return None, "⚠️ No se encontraron columnas de texto para analizar." | |
| # Generar nube de palabras | |
| # filepath = viz_23_nube_palabras_paloma(df_texto, columna, max_palabras=100) | |
| # Si falla, intentar versión simple | |
| # if not os.path.exists(filepath): | |
| filepath = viz_23_nube_palabras_simple(df_texto, columna, max_palabras=100) | |
| explicacion = f""" | |
| ## 🕊️ Nube de Palabras - Paloma de Paz | |
| ### ¿Qué muestra? | |
| Las **palabras más frecuentes** encontradas en el documento | |
| ### Interpretación: | |
| - **Tamaño grande**: Palabras que aparecen con más frecuencia | |
| - **Tamaño pequeño**: Palabras menos frecuentes | |
| - **Posición**: Aleatoria dentro de la forma de paloma | |
| ### 🔍 Análisis: | |
| - Total de registros analizados: {len(df_texto)} | |
| - Palabras únicas mostradas: ~100 | |
| - Stopwords excluidas: sí (artículos, preposiciones, etc.) | |
| ### 💡 Uso: | |
| Identifica rápidamente los **temas centrales** y **conceptos clave** | |
| que dominan el conjunto de textos analizados. | |
| ### 🎨 Simbología: | |
| La paloma de paz representa el espíritu de los Objetivos de Desarrollo | |
| Sostenible: paz, bienestar y desarrollo para todos. | |
| """ | |
| return filepath, explicacion | |
| def tab_estadisticas(df_global): | |
| # def tab_estadisticas(): | |
| """Pestaña con análisis estadístico detallado""" | |
| if not DATOS_CARGADOS: | |
| return "⚠️ Error: No se pudieron cargar los datos." | |
| # Estadísticas globales | |
| stats = df_global['similaridad_cos'].describe() | |
| correlacion = df_global['rank'].corr(df_global['similaridad_cos']) | |
| # Por ODS | |
| stats_ods = df_global.groupby('ods_id')['similaridad_cos'].agg([ | |
| ('count', 'count'), | |
| ('mean', 'mean'), | |
| ('std', 'std'), | |
| ('min', 'min'), | |
| ('max', 'max') | |
| ]).round(4) | |
| # Top 50 | |
| top_50_ods = df_global.nsmallest(50, 'rank')['ods_id'].value_counts() | |
| html = f""" | |
| <div style="font-family: Arial, sans-serif; padding: 20px;"> | |
| <h1 style="color: #2E5090;">📈 Análisis Estadístico Detallado</h1> | |
| <div class="stats-box" style="background-color: #E8F4F8; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h2 style="color: #2E5090;">1. Estadísticas Globales</h2> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Cantidad de datos:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['count']:.0f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Media:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['mean']:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Desviación Estándar:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['std']:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Mínimo:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['min']:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Q1 (Percentil 25):</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['25%']:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Mediana (Q2):</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['50%']:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Q3 (Percentil 75):</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['75%']:.4f}</td> | |
| </tr> | |
| <tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Máximo:</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{stats['max']:.4f}</td> | |
| </tr> | |
| </table> | |
| </div> | |
| <div class="explanation-box" style="background-color: #{'E6F7E6' if correlacion < -0.7 else 'FFF9E6'}; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #{'E6F7E6' if correlacion < -0.7 else 'FFD700'};"> | |
| <h2 style="color: #{'E6F7E6' if correlacion < -0.7 else 'FF8C00'};">2. Validación del Sistema</h2> | |
| <p style="font-size: 18px;"><strong>Correlación Rank vs Similaridad:</strong> {correlacion:.4f}</p> | |
| <p><strong>Interpretación:</strong> | |
| { | |
| "✅ Excelente - Sistema de ranking muy confiable" if correlacion < -0.9 else | |
| "✅ Muy bueno - Sistema de ranking confiable" if correlacion < -0.7 else | |
| "⚠️ Aceptable - Sistema funciona pero puede mejorarse" if correlacion < -0.4 else | |
| "❌ Problema - Revisar cálculo de similaridad o ranking" | |
| } | |
| </p> | |
| <p><em>Una correlación negativa fuerte indica que a mayor ranking (menos relevante), menor es la similaridad, lo cual es el comportamiento esperado.</em></p> | |
| </div> | |
| <div class="stats-box" style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h2 style="color: #2E5090;">3. Estadísticas por ODS</h2> | |
| <table style="width: 100%; border-collapse: collapse; font-size: 14px;"> | |
| <thead> | |
| <tr style="background-color: #FFD700;"> | |
| <th style="padding: 10px; text-align: left;">ODS</th> | |
| <th style="padding: 10px; text-align: right;">Count</th> | |
| <th style="padding: 10px; text-align: right;">Media</th> | |
| <th style="padding: 10px; text-align: right;">Std</th> | |
| <th style="padding: 10px; text-align: right;">Min</th> | |
| <th style="padding: 10px; text-align: right;">Max</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {''.join([f'''<tr> | |
| <td style="padding: 8px; border-bottom: 1px solid #ddd;"><strong>ODS {idx}</strong></td> | |
| <td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: right;">{int(row['count'])}</td> | |
| <td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: right;">{row['mean']:.4f}</td> | |
| <td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: right;">{row['std']:.4f}</td> | |
| <td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: right;">{row['min']:.4f}</td> | |
| <td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: right;">{row['max']:.4f}</td> | |
| </tr>''' for idx, row in stats_ods.iterrows()])} | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="explanation-box" style="background-color: #E8F4F8; padding: 20px; border-radius: 10px; margin: 20px 0; border-left: 5px solid #2E5090;"> | |
| <h2 style="color: #2E5090;">4. ODS Más Representados en Top 50</h2> | |
| <table style="width: 100%; border-collapse: collapse;"> | |
| <thead> | |
| <tr style="background-color: #4472C4; color: white;"> | |
| <th style="padding: 10px; text-align: left;">ODS</th> | |
| <th style="padding: 10px; text-align: right;">Cantidad</th> | |
| <th style="padding: 10px; text-align: right;">Porcentaje</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {''.join([f'''<tr> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>ODS {idx}</strong></td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{count}</td> | |
| <td style="padding: 10px; border-bottom: 1px solid #ddd; text-align: right;">{count/50*100:.1f}%</td> | |
| </tr>''' for idx, count in top_50_ods.head(10).items()])} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| """ | |
| return html | |
| # ============================================================================ | |
| # CONSTRUCCIÓN DE LA APLICACIÓN GRADIO | |
| # ============================================================================ | |
| def crear_app(): | |
| """Crea y configura la aplicación Gradio completa""" | |
| from src.embeddings.llm_clasificador_HF_gem import procesar_lote_llm | |
| with gr.Blocks( | |
| title="Sistema de Visualización ODS", | |
| # theme=gr.themes.Soft( | |
| # primary_hue="indigo", | |
| # secondary_hue="orange", | |
| # neutral_hue="slate" | |
| # ), | |
| theme=gr.themes.Default(), | |
| css=CUSTOM_CSS | |
| ) as app: | |
| gr.HTML(f""" | |
| <div class="header-institucional"> | |
| <div style="flex: 0 0 auto;"> | |
| <img src="{dict_logos['gobierno']}" | |
| alt="Gobierno de Colombia" | |
| class="logo-institucional"> | |
| </div> | |
| <div class="titulo-institucional"> | |
| <h1></h1> | |
| <p> </p> | |
| </div> | |
| <div style="flex: 0 0 auto;"> | |
| <img src="{dict_logos['fondo_un']}" | |
| alt="Fondo Multidonante de las Naciones Unidas" | |
| class="logo-institucional"> | |
| </div> | |
| </div> | |
| """) | |
| # Encabezado principal | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.HTML(f""" | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; flex: 1; min-width: 150px;"> | |
| <img src="{dict_logos['logo_conectaods']}" | |
| alt="Logo conectaODS" | |
| width="{int(200)}" | |
| style="margin: 0 auto; display: block;" /> | |
| </div> | |
| """) | |
| with gr.Column(scale=3): | |
| # gr.Markdown(f""" | |
| # # ConectaODS: Tu voz en clave de desarrollo sostenible | |
| # ### Explorador Interactivo | |
| # *ConectaODS es una herramienta basada en inteligencia artificial que traduce | |
| # relatos de iniciativas asociadas a problematicas y oportunidades a nivel territorial en lenguaje de Objetivos de Desarrollo Soistenible - ODS* | |
| # """) | |
| gr.HTML(f""" | |
| <style> | |
| .conectaods-header {{ | |
| padding: 30px 20px; | |
| text-align: left; | |
| }} | |
| .conectaods-main-title {{ | |
| font-size: 2.5em; | |
| font-weight: 700; | |
| color: #1a1a1a; | |
| margin: 15px 0; | |
| line-height: 1.3; | |
| letter-spacing: -0.5px; | |
| }} | |
| .conectaods-subtitle {{ | |
| font-size: 1.5em; | |
| font-weight: 500; | |
| color: #2E5090; | |
| margin: 12px 0 20px 0; | |
| letter-spacing: 0.5px; | |
| }} | |
| .conectaods-description {{ | |
| font-size: 1.15em; | |
| color: #555; | |
| line-height: 1.6; | |
| font-style: italic; | |
| margin: 15px 0; | |
| max-width: 600px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| }} | |
| @media (max-width: 768px) {{ | |
| .conectaods-main-title {{ | |
| font-size: 2em; | |
| }} | |
| .conectaods-subtitle {{ | |
| font-size: 1.2em; | |
| }} | |
| .conectaods-description {{ | |
| font-size: 1em; | |
| }} | |
| }} | |
| </style> | |
| <div class="conectaods-header"> | |
| <h1 class="conectaods-main-title">ConectaODS: Tu voz en clave de desarrollo sostenible</h1> | |
| <h2 class="conectaods-subtitle">Explorador Interactivo</h2> | |
| <p class="conectaods-description"> | |
| ConectaODS es una herramienta basada en inteligencia artificial que traduce | |
| relatos de iniciativas asociadas a problematicas y oportunidades a nivel territorial en lenguaje de Objetivos de Desarrollo Soistenible - ODS | |
| </p> | |
| </div> | |
| """) | |
| # Pestañas principales | |
| with gr.Tabs(): | |
| def ver_radio(df): | |
| return True | |
| # PESTAÑA: CONSULTA | |
| with gr.Tab("CONSULTA INDIVIDUAL PROMPT"): | |
| # txt_ods = gr.Textbox(value="ods", visible=False) | |
| # txt_meta = gr.Textbox(value="meta", visible=False) | |
| # txt_indicador = gr.Textbox(value="indicador", visible=False) | |
| with gr.Column(): | |
| gr.Markdown(f""" | |
| ### Cuéntanos una iniciativa de solución asociada a una problemática u oportunidad de tu territorio. | |
| *La herramienta analizará el texto y mostrará con qué Objetivos y de Desarrollo Sostenible (ODS) y metas se relaciona, | |
| facilitando la comprensión y la priorización de acciones para respuesta de transformación integral.* | |
| """) | |
| query_in_prt = gr.Textbox(lines=5, placeholder="Escribe aquí tu consulta...", label="Iniciativa a analizar") | |
| lst_query_in_prt = gr.State() #gr.Textbox(value="[]", visible=False) | |
| def add_query_list(query): | |
| return gr.update(value=[query]) | |
| query_in_prt.change( | |
| fn = add_query_list, | |
| inputs = query_in_prt, | |
| outputs = lst_query_in_prt) | |
| query_out_prt = gr.Textbox(lines=5, label="Texto ajustado para lenguaje natural", visible=False) | |
| txt_municipio = gr.Textbox(lines=1, value="Municipio de consulta ...", label="Municipio", interactive=True) | |
| btn_prt = gr.Button(value="Analizar texto en lenguaje de ODS") | |
| msj_procesamiento_prt = gr.Textbox(value="... preparando análisis de iniciativa.", label='' , visible=True) | |
| with gr.Tab("🔍 Resultado"): | |
| with gr.Row(visible=False): | |
| ods_prt = gr.Dataframe( label="ODS")#, buttons=["fullscreen"]) | |
| meta_prt = gr.Dataframe( label="METAS")#, buttons=["fullscreen"]) | |
| indicador_prt = gr.Dataframe( label="INDICADORES")#, buttons=["fullscreen"]) | |
| html_inicio_ods_prt = gr.HTML() #tab_inicio(ods.value) | |
| #### Actualizando resultados iniciales | |
| ods_prt.change( | |
| fn=tab_inicio_prompt, | |
| inputs=[ods_prt,meta_prt,indicador_prt], | |
| outputs=[html_inicio_ods_prt] | |
| ) | |
| meta_prt.change( | |
| fn=tab_inicio_prompt, | |
| inputs=[ods_prt,meta_prt,indicador_prt], | |
| outputs=[html_inicio_ods_prt] | |
| ) | |
| indicador_prt.change( | |
| fn=tab_inicio_prompt, | |
| inputs=[ods_prt,meta_prt,indicador_prt], | |
| outputs=[html_inicio_ods_prt] | |
| ) | |
| # Función wrapper para convertir string a lista | |
| def procesar_consulta(query_text): | |
| """Convierte el texto de consulta en una lista y llama a procesar_lote""" | |
| if not query_text or not query_text.strip(): | |
| return pd.DataFrame(), pd.DataFrame(), pd.DataFrame() | |
| # Pasar como lista con un único elemento | |
| return procesar_lote_llm([query_text.strip()]) | |
| with gr.Tab("Resultados por ODS - Metas"): | |
| with gr.Row(): | |
| with gr.Tab("🏆 Ranking Masivo ODS"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_ods_ind = gr.Plot(label="Ranking Global ODS") | |
| with gr.Column(scale=1): | |
| exp_mass_ods_ind = gr.Markdown() | |
| with gr.Tab('Tabla score ODS'): | |
| output_ods_mass_mix_ind = gr.Dataframe(label="Resultados del análisis masivo ODS") | |
| with gr.Row(): | |
| with gr.Tab("🏆 Ranking Masivo METAS"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_metas_ind = gr.Plot(label="Ranking Global METAS") | |
| with gr.Column(scale=1): | |
| exp_mass_metas_ind = gr.Markdown() | |
| with gr.Tab('Tabla score METAS'): | |
| output_meta_mass_mix_ind = gr.Dataframe(label="Resultados del análisis masivo METAS") | |
| with gr.Row(visible=False): | |
| with gr.Tab("🏆 Ranking Masivo INDICADORES"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_indicadores_ind = gr.Plot(label="Ranking Global INDICADORES") | |
| with gr.Column(scale=1): | |
| exp_mass_indicadores_ind = gr.Markdown() | |
| with gr.Tab('Tabla score INDICADORES'): | |
| output_indicadores_mass_mix_ind = gr.Dataframe(label="Resultados del análisis masivo INDICADORES") | |
| ## logicas de actualizaciones | |
| with gr.Row(visible=False): | |
| ods_prt.change( | |
| fn=tab_viz21, | |
| inputs=[ods_prt, gr.State('ods')], | |
| outputs=[plot_mass_ods_ind, exp_mass_ods_ind] | |
| ) | |
| meta_prt.change( | |
| fn=tab_viz21, | |
| inputs=[meta_prt, gr.State('meta')], | |
| outputs=[plot_mass_metas_ind, exp_mass_metas_ind] | |
| ) | |
| indicador_prt.change( | |
| fn=tab_viz21, | |
| inputs=[indicador_prt, gr.State('indicador')], | |
| outputs=[plot_mass_indicadores_ind, exp_mass_indicadores_ind] | |
| ) | |
| btn_prt.click(procesar_consulta, query_in_prt, [ods_prt,meta_prt,indicador_prt,msj_procesamiento_prt], show_progress=True) | |
| btn_prt.click(procesar_consulta, query_in_prt, [output_ods_mass_mix_ind,output_meta_mass_mix_ind,output_indicadores_mass_mix_ind,msj_procesamiento_prt], show_progress=True) | |
| txt_fecha = gr.Textbox(value="Fecha de consulta: ", interactive=False, show_label=False) | |
| ods_prt.change(fn= actualizar_fecha, inputs=[], outputs=txt_fecha) | |
| # btn_prt.click(procesar_lote, query_in_prt, [ods_prt,meta_prt,indicador_prt], show_progress=True) | |
| with gr.Tab("CONSULTA INDIVIDUAL", visible=False): | |
| txt_ods = gr.Textbox(value="ods", visible=False) | |
| txt_meta = gr.Textbox(value="meta", visible=False) | |
| txt_indicador = gr.Textbox(value="indicador", visible=False) | |
| with gr.Column(): | |
| gr.Markdown(f""" | |
| ### Cuéntanos una iniciativa, problema o propuesta de tu territorio. | |
| *La herramienta analizará el texto y mostrará con qué | |
| Objetivos de Desarrollo Sostenible (ODS) se | |
| relaciona.* | |
| """) | |
| query_in = gr.Textbox(lines=5, placeholder="Escribe aquí tu consulta...", label="Iniciativa a analizar") | |
| query_out = gr.Textbox(lines=5, label="Texto ajustado para lenguaje natural", visible=False) | |
| btn = gr.Button(value="Analizar mi iniciativa") | |
| msj_procesamiento = gr.Textbox(value="... preparando análisis de iniciativa.", visible=True) | |
| with gr.Tab("🔍 Resultado"): | |
| with gr.Row(): | |
| ods = gr.Dataframe( label="ODS", visible=False)#, buttons=["fullscreen"]) | |
| meta = gr.Dataframe( label="METAS", visible=False)#, buttons=["fullscreen"]) | |
| indicador = gr.Dataframe( label="INDICADORES", visible=False)#, buttons=["fullscreen"]) | |
| html_inicio_ods = gr.HTML() #tab_inicio(ods.value) | |
| # with gr.Row(): | |
| # html_pareto_ods = gr.HTML() #tab_viz20(df_global) | |
| # html_pareto_meta = gr.HTML() | |
| # html_pareto_indicador = gr.HTML() | |
| # PESTAÑA 20: DIAGRAMA DE PARETO | |
| with gr.Tab("📊 Priorización con Pareto", visible=False): | |
| gr.HTML(""" | |
| <div style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #F0F0F0;">📊 Diagrama de Pareto (Regla 80/20)</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>CRITERIO:</strong> Aplicamos un criterio de Pareto ADAPTATIVO calibrado para contextos de clasificación multinivel, | |
| donde el umbral se ajusta según la distribución empírica de similaridad y la granularidad requerida para toma de decisiones.</li> | |
| <li><strong>Marcos globales:</strong> (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS | |
| prioritarios por ciclo de implementación.</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones son dinámicas y permiten exploración</li> | |
| </ol> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plotOds = gr.Plot(label="Diagrama de Pareto ODS") | |
| with gr.Column(scale=1): | |
| slider_ods_pareto = gr.Slider(0.1, 1.0, step=0.1, value=0.7,interactive=True, label="Top ODS") | |
| expOds = gr.Markdown() | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plotMeta = gr.Plot(label="Diagrama de Pareto Metas") | |
| with gr.Column(scale=1): | |
| slider_meta_pareto = gr.Slider(0.1, 1.0, step=0.1, value=0.7, interactive=True, label="Top Metas") | |
| expMeta = gr.Markdown() | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plotIndicador = gr.Plot(label="Diagrama de Pareto Indicadores") | |
| with gr.Column(scale=1): | |
| slider_indicador_pareto = gr.Slider(0.1, 1.0, step=0.1, value=0.7, interactive=True, label="Top Indicadores") | |
| expIndicador = gr.Markdown() | |
| # btn0_i = gr.Button("🔄 Traducción a ODS", variant="primary") | |
| ### Actualizando resultados iniciales | |
| ods.change( | |
| fn=tab_inicio, | |
| inputs=[ods,meta,indicador], | |
| outputs=[html_inicio_ods] | |
| ) | |
| meta.change( | |
| fn=tab_inicio, | |
| inputs=[ods,meta,indicador], | |
| outputs=[html_inicio_ods] | |
| ) | |
| indicador.change( | |
| fn=tab_inicio, | |
| inputs=[ods,meta,indicador], | |
| outputs=[html_inicio_ods] | |
| ) | |
| ### Actualizando análisis de Pareto | |
| ods.change( | |
| fn=tab_viz20, | |
| inputs=[ods, gr.State('ods'), gr.State(0.7), gr.State(False), gr.State('individual')], | |
| outputs=[plotOds, expOds] | |
| ) | |
| meta.change( | |
| fn=tab_viz20, | |
| inputs=[meta, gr.State('meta'), gr.State(0.7), gr.State(False), gr.State('individual')], | |
| outputs=[plotMeta, expMeta] | |
| ) | |
| indicador.change( | |
| fn=tab_viz20, | |
| inputs=[indicador, gr.State('indicador'), gr.State(0.7), gr.State(False), gr.State('individual')], | |
| outputs=[plotIndicador, expIndicador] | |
| ) | |
| ### Actualizando nivel de pareto | |
| slider_ods_pareto.change( | |
| fn=tab_viz20, | |
| inputs=[ods, gr.State('ods'), slider_ods_pareto, gr.State(False), gr.State('individual')], | |
| outputs=[plotOds, expOds] | |
| ) | |
| slider_meta_pareto.change( | |
| fn=tab_viz20, | |
| inputs=[meta, gr.State('meta'), slider_meta_pareto, gr.State(False), gr.State('individual')], | |
| outputs=[plotMeta, expMeta] | |
| ) | |
| slider_indicador_pareto.change( | |
| fn=tab_viz20, | |
| inputs=[indicador, gr.State('indicador'), slider_indicador_pareto, gr.State(False), gr.State('individual')], | |
| outputs=[plotIndicador, expIndicador] | |
| ) | |
| with gr.Tab("📋 Tablas Detalladas"): | |
| es_visible = gr.State(value=False) | |
| with gr.Row(visible=es_visible): | |
| slider_ods = gr.Slider(1, 5, step=1, value=3,interactive=True, label="Top ODS") | |
| slider_meta = gr.Slider(1, 5, step=1, value=3, interactive=True, label="Top Metas") | |
| slider_indicador = gr.Slider(1, 5, step=1, value=3, interactive=True, label="Top Indicadores") | |
| def len_dfs(df): | |
| return gr.update(maximum=len(df)) | |
| ods.change(fn=len_dfs, | |
| inputs=[ods], | |
| outputs=[slider_ods], api_name="ods") | |
| meta.change(fn=len_dfs, | |
| inputs=[meta], | |
| outputs=[slider_meta], api_name="ods") | |
| indicador.change(fn=len_dfs, | |
| inputs=[indicador], | |
| outputs=[slider_indicador], api_name="ods") | |
| with gr.Row(): | |
| df_ods_filtrado = gr.Dataframe(label="ODS Filtrados") | |
| df_meta_filtrado = gr.Dataframe(label="METAS Filtradas") | |
| df_indicador_filtrado = gr.Dataframe(label="INDICADORES Filtrados") | |
| with gr.Row(): | |
| slider_ods.release(fn=lambda df, n: df[df[f'ods_rank'] <= n][['ods_rank','ODS_ID','OBJETIVO']] if df is not None else None, | |
| inputs=[ods, slider_ods], | |
| outputs=[df_ods_filtrado], api_name="ods") | |
| slider_meta.release(fn=lambda df, n: df[df[f'meta_rank'] <= n][['meta_rank','META_ID','META']] if df is not None else None, | |
| inputs=[meta, slider_meta], | |
| outputs=[df_meta_filtrado], api_name="ods") | |
| slider_indicador.release(fn=lambda df, n: df[df[f'indicador_rank'] <= n][['indicador_rank','INDICADOR_ID','INDICADOR']] if df is not None else None, | |
| inputs=[indicador, slider_indicador], | |
| outputs=[df_indicador_filtrado], api_name="ods") | |
| # with gr.Row(): | |
| # genero = gr.Dataframe( label="Enfoque de genero") | |
| # poblacional = gr.Dataframe( label="Enfoque poblacional") | |
| # etnico = gr.Dataframe( label="Enfoque étnico") | |
| # with gr.Row(): | |
| # pilar = gr.Dataframe( label="Pilares") | |
| # estrategia = gr.Dataframe( label="Estrategias") | |
| # categoria = gr.Dataframe( label="Categorias") | |
| with gr.Row(visible=False): | |
| bdl_ods = gr.Dataframe(value=pd.DataFrame(), label="BDL_ODS") | |
| # with gr.Row(): | |
| # ods_plot = gr.ScatterPlot( | |
| # value=pd.DataFrame(), | |
| # x="ODS_ID", | |
| # y="ods_rank", | |
| # sort="-y", | |
| # label="Gráfico de Barras ODS", | |
| # # color="cuisine", | |
| # # x_bin=1, | |
| # color_map={ | |
| # "1": "#E5243B", # Red - ODS 1: Fin de la Pobreza | |
| # "2": "#DDA63A", # Mustard - ODS 2: Hambre Cero | |
| # "3": "#4C9F38", # Kelly Green - ODS 3: Salud y Bienestar | |
| # "4": "#C5192D", # Dark Red - ODS 4: Educación de Calidad | |
| # "5": "#FF3A21", # Red Orange - ODS 5: Igualdad de Género | |
| # "6": "#26BDE2", # Bright Blue - ODS 6: Agua Limpia y Saneamiento | |
| # "7": "#FCC30B", # Yellow - ODS 7: Energía Asequible y No Contaminante | |
| # "8": "#A21942", # Burgundy Red - ODS 8: Trabajo Decente y Crecimiento Económico | |
| # "9": "#FD6925", # Orange - ODS 9: Industria, Innovación e Infraestructura | |
| # "10": "#DD1367", # Magenta - ODS 10: Reducción de las Desigualdades | |
| # "11": "#FD9D24", # Golden Yellow - ODS 11: Ciudades y Comunidades Sostenibles | |
| # "12": "#BF8B2E", # Dark Mustard - ODS 12: Producción y Consumo Responsables | |
| # "13": "#3F7E44", # Dark Green - ODS 13: Acción por el Clima | |
| # "14": "#0A97D9", # Blue - ODS 14: Vida Submarina | |
| # "15": "#56C02B", # Lime Green - ODS 15: Vida de Ecosistemas Terrestres | |
| # "16": "#00689D", # Royal Blue - ODS 16: Paz, Justicia e Instituciones Sólidas | |
| # "17": "#19486A", # Navy Blue - ODS 17: Alianzas para Lograr los Objetivos | |
| # }, | |
| # ) | |
| # def actualizar_ods_plot(df, n): | |
| # if df is not None: | |
| # df_filtrado = df[df['ods_rank'] <= n] | |
| # df_filtrado['ODS_ID'] = df_filtrado['ODS_ID'].astype(str) | |
| # # df_filtrado['ods_rank'] = df_filtrado['ods_rank'].astype(int) | |
| # return df_filtrado | |
| # else: | |
| # return pd.DataFrame() | |
| # slider_ods.release(fn=lambda df, n: (df[df[f'ods_rank'] <= n], actualizar_ods_plot(df, n)) if df is not None else None, | |
| # inputs=[ods, slider_ods], | |
| # outputs=[df_ods_filtrado, ods_plot], api_name="ods") | |
| # slider_ods.release(fn=lambda df, n: df[df[f'ods_rank'] <= n]['ODS_ID'].astype(str) if df is not None else None, | |
| # inputs=[ods, slider_ods], | |
| # outputs=[df_ods_filtrado, ods_plot], api_name="ods") | |
| # with gr.Tab("🔄 Pivot Table Iniciativa Individual"): | |
| # with gr.Row(): | |
| # with gr.Column(scale=3): | |
| # html18_ods = gr.HTML(label="Pivot Table Interactivo ODS", elem_id="pivot-container") | |
| # html18_ods_f = gr.File(label="📥 Descargar Pivot Table (HTML)") #gr.HTML(label="Pivot Table Interactivo ODS", elem_id="pivot-container") | |
| # html18_meta = gr.HTML(label="Pivot Table Interactivo METAS", elem_id="pivot-container") | |
| # html18_meta_f = gr.File(label="📥 Descargar Pivot Table (HTML)") #gr.HTML(label="Pivot Table Interactivo METAS", elem_id="pivot-container") | |
| # html18_indicador = gr.HTML(label="Pivot Table Interactivo INDICADORES", elem_id="pivot-container") | |
| # html18_indicador_f = gr.File(label="📥 Descargar Pivot Table (HTML)") #gr.HTML(label="Pivot Table Interactivo INDICADORES", elem_id="pivot-container") | |
| # with gr.Column(scale=1): | |
| # exp18 = gr.Markdown() | |
| # # def actualizar_pivots(df_ods, df_meta, df_indicador, slider_ods, slider_meta, slider_indicador): | |
| # def actualizar_pivots(df_ods, slider_ods): | |
| # # Actualizar ODS | |
| # if df_ods is not None and len(df_ods) > 0: | |
| # html_ods, exp_ods, file_ods = tab_viz18(df_ods, "ods", slider_ods) | |
| # else: | |
| # html_ods = gr.update(value="<p>No hay datos ODS para mostrar</p>") | |
| # exp_ods = gr.update(value="## Sin datos ODS") | |
| # file_ods = gr.update(value=None) | |
| # # # Actualizar METAS | |
| # # if df_meta is not None and len(df_meta) > 0: | |
| # # html_meta, exp_meta, file_meta = tab_viz18(df_meta, "meta", slider_meta) | |
| # # else: | |
| # # html_meta = gr.update(value="<p>No hay datos METAS para mostrar</p>") | |
| # # exp_meta = gr.update(value="## Sin datos METAS") | |
| # # file_meta = gr.update(value=None) | |
| # # # Actualizar INDICADORES | |
| # # if df_indicador is not None and len(df_indicador) > 0: | |
| # # html_indicador, exp_indicador, file_indicador = tab_viz18(df_indicador, "indicador", slider_indicador) | |
| # # else: | |
| # # html_indicador = gr.update(value="<p>No hay datos INDICADORES para mostrar</p>") | |
| # # exp_indicador = gr.update(value="## Sin datos INDICADORES") | |
| # # file_indicador = gr.update(value=None) | |
| # # Crear explicación combinada | |
| # explicacion_combinada = """ | |
| # ## 🔄 Tablas Dinámicas Interactivas | |
| # **Arrastra campos** desde la lista de la izquierda al área central para analizar datos. | |
| # **Funcionalidades:** | |
| # - 📊 Cambia entre tabla, gráfico de barras, línea, etc. | |
| # - 🔢 Modifica agregaciones (suma, promedio, cuenta) | |
| # - 🎯 Filtra datos con arrastrar y soltar | |
| # - 📥 Descarga el HTML para uso externo | |
| # **Nota:** Cada tabla es independiente y se actualiza automáticamente. | |
| # """ | |
| # # return ( | |
| # # html_ods, html_meta, html_indicador, | |
| # # gr.update(value=explicacion_combinada), | |
| # # file_ods, file_meta, file_indicador | |
| # # ) | |
| # return ( | |
| # html_ods, | |
| # gr.update(value=explicacion_combinada), | |
| # file_ods | |
| # ) | |
| # # CONEXIÓN ÚNICA (cuando cambien los 3 dataframes) | |
| # # for df_component in [df_ods_filtrado, df_meta_filtrado, df_indicador_filtrado]: | |
| # df_ods_filtrado.change( | |
| # fn=actualizar_pivots, | |
| # inputs=[df_ods_filtrado, slider_ods], | |
| # outputs=[ | |
| # html18_ods, #html18_meta, html18_indicador, | |
| # exp18, | |
| # html18_ods_f, #html18_meta_f, html18_indicador_f | |
| # ], | |
| # show_progress=False | |
| # ) | |
| # df_meta_filtrado.change( | |
| # fn=actualizar_pivots, | |
| # inputs=[df_meta_filtrado, slider_meta], | |
| # outputs=[ | |
| # # html18_ods, html18_meta, html18_indicador, | |
| # html18_meta, | |
| # exp18, | |
| # # html18_ods_f, html18_meta_f, html18_indicador_f | |
| # html18_meta_f | |
| # ], | |
| # show_progress=False | |
| # ) | |
| # df_indicador_filtrado.change( | |
| # fn=actualizar_pivots, | |
| # inputs=[df_indicador_filtrado, slider_indicador], | |
| # outputs=[ | |
| # # html18_ods, html18_meta, html18_indicador, | |
| # html18_indicador, | |
| # exp18, | |
| # # html18_ods_f, html18_meta_f, html18_indicador_f | |
| # html18_indicador_f | |
| # ], | |
| # show_progress=False | |
| # ) | |
| # btn.click(search, query_in, [query_out,ods,meta,indicador,genero,poblacional,etnico,pilar,estrategia,categoria,bdl_ods]) | |
| btn.click(search, query_in, [query_out,ods,meta,indicador,bdl_ods, msj_procesamiento], show_progress=True) | |
| btn.click(ver_radio, inputs=[], outputs=[es_visible]) | |
| # btn.click(fn=tab_viz18, inputs=[ods, txt_ods], outputs=[html18_ods, exp18, html18_ods_f]) | |
| # btn.click(fn=tab_viz18, inputs=[meta, txt_meta], outputs=[html18_meta, exp18, html18_meta_f]) | |
| # btn.click(fn=tab_viz18, inputs=[indicador, txt_indicador], outputs=[html18_indicador, exp18, html18_indicador_f]) | |
| with gr.Tab('CONSULTA ESPECIALIZADA', visible=False): | |
| # with gr.Tab("CONSULTA"): | |
| with gr.Column(): | |
| query_in_esp = gr.Textbox(lines=5, placeholder="Escribe aquí tu consulta...", label="Iniciativa a analizar") | |
| query_out_esp = gr.Textbox(lines=5, label="Texto ajustado para lenguaje natural", visible=False) | |
| btn_esp = gr.Button(value="Analizar mi iniciativa") | |
| # lvl = gr.Dropdown([col for col in bdl_ods_esp.value.columns if 'ID' in col], label='Nivel de análisis') | |
| # score = gr.Dropdown([col for col in bdl_ods_esp.value.columns if 'similaridad' in col], label='Score de medida') | |
| # rank = gr.Dropdown([col for col in bdl_ods_esp.value.columns if 'rank' in col], label='Score de medida') | |
| with gr.Tab("Clasificaciones"): | |
| with gr.Row(): | |
| ods_esp = gr.Dataframe(value=pd.DataFrame(), label="ODS") | |
| meta_esp = gr.Dataframe(value=pd.DataFrame(), label="METAS") | |
| indicador_esp = gr.Dataframe(value=pd.DataFrame(), label="INDICADORES") | |
| with gr.Row(): | |
| genero_esp = gr.Dataframe(value=pd.DataFrame(), label="Enfoque de genero") | |
| poblacional_esp = gr.Dataframe(value=pd.DataFrame(), label="Enfoque poblacional") | |
| etnico_esp = gr.Dataframe(value=pd.DataFrame(), label="Enfoque étnico") | |
| with gr.Row(): | |
| pilar_esp = gr.Dataframe(value=pd.DataFrame(), label="Pilares") | |
| estrategia_esp = gr.Dataframe(value=pd.DataFrame(), label="Estrategias") | |
| categoria_esp = gr.Dataframe(value=pd.DataFrame(), label="Categorias") | |
| with gr.Row(): | |
| bdl_ods_esp = gr.Dataframe(value=pd.DataFrame(), label="ODS") | |
| # PESTAÑA: INICIO | |
| with gr.Tab("🏠 Inicio"): | |
| html_inicio_ods = gr.HTML() #tab_inicio(ods.value) | |
| btn0 = gr.Button("🔄 Generar Metricas Iniciales", variant="primary") | |
| btn0.click( | |
| fn=tab_inicio, | |
| inputs=[ods_esp,meta_esp,indicador_esp], | |
| outputs=[html_inicio_ods] | |
| ) | |
| # PESTAÑA 1: BOX PLOT | |
| with gr.Tab("📦 1. Box Plot"): | |
| btn1 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| exp1 = gr.Markdown() | |
| with gr.Row(visible=False): | |
| with gr.Column(scale=2): | |
| plot1_1 = gr.Plot(label="Diagrama de Caja por ODS") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot1_2 = gr.Plot(label="Diagrama de Caja por META") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot1_3 = gr.Plot(label="Diagrama de Caja por INDICADOR") | |
| btn1.click( | |
| fn=tab_viz1, | |
| inputs=[ods_esp, meta_esp, indicador_esp], | |
| outputs=[plot1_1, plot1_2, plot1_3, exp1] | |
| ) | |
| # PESTAÑA 2: HEATMAP | |
| with gr.Tab("🔥 2. Heatmap"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| img2 = gr.Image(label="Mapa de Calor ODS × Ranking") | |
| with gr.Column(scale=1): | |
| exp2 = gr.Markdown() | |
| btn2 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn2.click( | |
| fn=tab_viz2, | |
| inputs=[ods_esp], | |
| outputs=[img2, exp2] | |
| ) | |
| # PESTAÑA 3: SCATTER 3D | |
| with gr.Tab("🌐 3. Scatter 3D"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot3 = gr.Plot(label="Gráfico 3D Interactivo") | |
| with gr.Column(scale=1): | |
| exp3 = gr.Markdown() | |
| btn3 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn3.click( | |
| fn=tab_viz3, | |
| inputs=[ods_esp], | |
| outputs=[plot3, exp3] | |
| ) | |
| # PESTAÑA 4: RADAR | |
| with gr.Tab("🕸️ 4. Radar Chart"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot4 = gr.Plot(label="Gráfico de Radar") | |
| with gr.Column(scale=1): | |
| exp4 = gr.Markdown() | |
| btn4 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn4.click( | |
| fn=tab_viz4, | |
| inputs=[ods_esp], | |
| outputs=[plot4, exp4] | |
| ) | |
| # PESTAÑA 5: SUNBURST | |
| with gr.Tab("☀️ 5. Sunburst"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot5 = gr.Plot(label="Diagrama de Sol") | |
| with gr.Column(scale=1): | |
| exp5 = gr.Markdown() | |
| btn5 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn5.click( | |
| fn=tab_viz5, | |
| inputs=[ods_esp], | |
| outputs=[plot5, exp5] | |
| ) | |
| # PESTAÑA 6: TOP INDICADORES | |
| # with gr.Tab("🏆 6. Top Indicadores"): | |
| # with gr.Row(): | |
| # with gr.Column(scale=2): | |
| # plot6 = gr.Plot(label="Top 5 Indicadores por ODS") | |
| # with gr.Column(scale=1): | |
| # exp6 = gr.Markdown() | |
| # btn6 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| # btn6.click( | |
| # fn=tab_viz6, | |
| # inputs=[df, id_lvl, score, rank, titulo, top_n=3], | |
| # outputs=[plot6, exp6] | |
| # ) | |
| # PESTAÑA 7: STREAM GRAPH | |
| with gr.Tab("🌊 7. Stream Graph"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot7 = gr.Plot(label="Gráfico de Flujo") | |
| with gr.Column(scale=1): | |
| exp7 = gr.Markdown() | |
| btn7 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn7.click( | |
| fn=tab_viz7, | |
| inputs=[ods_esp], | |
| outputs=[plot7, exp7] | |
| ) | |
| # PESTAÑA 8: VIOLIN PLOT | |
| with gr.Tab("🎻 8. Violin Plot"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot8 = gr.Plot(label="Gráfico de Violín") | |
| with gr.Column(scale=1): | |
| exp8 = gr.Markdown() | |
| btn8 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn8.click( | |
| fn=tab_viz8, | |
| inputs=[ods_esp], | |
| outputs=[plot8, exp8] | |
| ) | |
| # PESTAÑA 9: DASHBOARD | |
| with gr.Tab("📊 9. Dashboard"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot9 = gr.Plot(label="Dashboard Integrado") | |
| with gr.Column(scale=1): | |
| exp9 = gr.Markdown() | |
| btn9 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn9.click( | |
| fn=tab_viz9, | |
| inputs=[ods_esp], | |
| outputs=[plot9, exp9] | |
| ) | |
| # PESTAÑA 10: MATRIZ TRANSICIÓN | |
| with gr.Tab("🔀 10. Matriz Transición"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| img10 = gr.Image(label="Matriz de Transición") | |
| with gr.Column(scale=1): | |
| exp10 = gr.Markdown() | |
| btn10 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| btn10.click( | |
| fn=tab_viz10, | |
| inputs=[ods_esp], | |
| outputs=[img10, exp10] | |
| ) | |
| # PESTAÑA: ESTADÍSTICAS | |
| with gr.Tab("📈 Estadísticas"): | |
| html_stats = gr.HTML() #tab_estadisticas(ods) | |
| btn11 = gr.Button("🔄 Generar Estadísticas", variant="primary") | |
| btn11.click( | |
| fn=tab_estadisticas, | |
| inputs=[ods_esp], | |
| outputs=[html_stats] | |
| ) | |
| btn_esp.click(search, query_in_esp, [query_out_esp,ods_esp,meta_esp,indicador_esp,genero_esp,poblacional_esp,etnico_esp,pilar_esp,estrategia_esp,categoria_esp,bdl_ods_esp]) | |
| with gr.Tab("CONSULTA MASIVA"): | |
| client = InferenceClient() | |
| gr.Markdown("Aquì puedes cargar un archivo con múltiples iniciativas de soluciòn asociadas a problematicas u oportunidades de tu territorio.") | |
| def procesar_archivo(file): | |
| if file is None: | |
| return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), [], 'Error al procesar el archivo: no se ha cargado ningún archivo' | |
| try: | |
| return search_mass(file, 3,3,3) | |
| except Exception as e: | |
| print("Error al leer el archivo:", e) | |
| return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), [], 'Error al procesar el archivo' | |
| def upload_file(filepath): | |
| name = Path(filepath).name | |
| return gr.update(label=f"Procesar {name}", value=filepath, visible=True) | |
| def pre_visual_df(file): | |
| if file is None: | |
| return gr.update(value=None) | |
| try: | |
| df = pd.read_excel(file) | |
| return gr.update(value=df.head(10)) | |
| except Exception as e: | |
| print("Error al leer el archivo:", e) | |
| return gr.update(value=None) | |
| def num_iniciaticas(file): | |
| if file is None: | |
| return gr.update(value=None) | |
| try: | |
| df = pd.read_excel(file) | |
| return gr.update(value=len(df['iniciativa'].dropna().unique().tolist())) | |
| except Exception as e: | |
| print("Error al leer el archivo:", e) | |
| return gr.update(value=0) | |
| with gr.Row(): | |
| # d = gr.DownloadButton(label=f"Descargar plantilla_iniciativas.xlsx", value=Path('data/plantillas/plantilla_iniciativas.xlsx'), visible=True) | |
| d = gr.DownloadButton(label=f"Descargar plantilla para consulta masiva", value=Path('data/raw/plantilla_iniciativas.xlsx'), visible=True) | |
| with gr.Column(): | |
| file_output = gr.File() | |
| u = gr.UploadButton("Upload a file", file_count="single", visible=False) | |
| pre_df = gr.Dataframe(label="Vista previa de las primeras 10 filas del archivo cargado") | |
| # btn_prev_df = gr.Button("Previsualizar archivo") | |
| u.upload(upload_file, u, file_output) | |
| file_output.change(pre_visual_df, file_output, pre_df) | |
| # btn_prev_df.click(pre_visual_df, u, pre_df) | |
| # PESTAÑA: CONSULTA | |
| def leer_tabla(file_input, col_iniciativa='iniciativa'): | |
| if file_input is None: | |
| return gr.update(value=None) | |
| df = pd.read_excel(file_input) | |
| return gr.update(value=df[['id_unico','iniciativa']]) | |
| df_tx_iniciativas = gr.Dataframe(label="Vista previa de las primeras 10 filas del archivo cargado", visible=False) | |
| with gr.Tab("MASIVA"): | |
| # DataFrame vacío con estructura de iniciativas | |
| df_iniciativas_template = pd.DataFrame(columns=['id_unico', 'iniciativa', 'ODS_ID', 'META_ID', 'INDICADOR_ID', 'objetivo', 'ods_rank']) | |
| titulo_mass = gr.HTML() | |
| def actualizar_titulo(filepath): | |
| try: | |
| name = Path(filepath).name | |
| return gr.update(value=f""" | |
| <style> | |
| .fuente-info-header {{ | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-left: 6px solid #2E5090; | |
| border-radius: 8px; | |
| padding: 20px 30px; | |
| margin: 15px 0; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| font-family: 'Arial', sans-serif; | |
| }} | |
| .fuente-titulo {{ | |
| color: #2E5090; | |
| font-size: 1.8em; | |
| font-weight: 700; | |
| margin: 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| }} | |
| .fuente-icono {{ | |
| font-size: 1.2em; | |
| }} | |
| .fuente-subtitulo {{ | |
| color: #555; | |
| font-size: 1.1em; | |
| font-weight: 400; | |
| margin: 8px 0 0 0; | |
| font-style: italic; | |
| }} | |
| @media (max-width: 768px) {{ | |
| .fuente-titulo {{ | |
| font-size: 1.4em; | |
| }} | |
| .fuente-subtitulo {{ | |
| font-size: 0.95em; | |
| }} | |
| }} | |
| </style> | |
| <div class="fuente-info-header"> | |
| <h2 class="fuente-titulo"> | |
| <span class="fuente-icono">📂</span> | |
| Fuente de Información: {name.replace('.xlsx','')} | |
| </h2> | |
| <p class="fuente-subtitulo">Análisis masivo de iniciativas territoriales</p> | |
| </div> | |
| """) | |
| except: | |
| return '' | |
| file_output.change(actualizar_titulo, file_output, titulo_mass) | |
| with gr.Column(): | |
| btn_mass_emb_pmt = gr.Button("Analizar archivo en lenguaje de ODS") | |
| msj_procesamiento_mass_pmt = gr.Textbox(value="... preparando para procesar documento", interactive=False, show_label=False) | |
| # PESTAÑA 19: RESUMEN EN TAGS | |
| # with gr.Tab("🏷️ 19. Resumen Tags"): | |
| with gr.Row(): | |
| with gr.Row(): | |
| # with gr.Column(scale=1): | |
| # html19_1_pmt = gr.HTML(label="Resumen en Tags") | |
| # with gr.Row(visible=False): | |
| # html19_2_pmt = gr.HTML(label="Resumen en Tags") | |
| # # with gr.Column(scale=4): | |
| # html19_3_pmt = gr.HTML(label="Resumen en Tags") | |
| # with gr.Column(scale=4): | |
| file_output.change(leer_tabla, | |
| file_output, | |
| df_tx_iniciativas) | |
| with gr.Column(scale=2): | |
| def burbuja_nini(n_iniciativas): | |
| try: | |
| return f"""<div style="margin-bottom: 40px;"> | |
| <h3 style="color: #4472C4; margin-bottom: 15px;">Métricas Generales</h3> | |
| <div style="display: flex; flex-wrap: wrap; gap: 15px;"> | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 14px; opacity: 0.9;">Iniciativas Analizadas</div> | |
| <div style="font-size: 36px; font-weight: bold; margin-top: 5px;">{n_iniciativas}</div> | |
| </div> | |
| </div> | |
| </div>""" | |
| except: | |
| return "" | |
| n_ini_state = gr.State(value=0) | |
| html_nini = gr.HTML() | |
| file_output.change(num_iniciaticas, file_output, n_ini_state) | |
| n_ini_state.change(burbuja_nini, n_ini_state, html_nini) | |
| with gr.Column(scale=3): | |
| img24_pmt = gr.Image(label="Nube de Palabras", type="filepath") | |
| with gr.Column(visible=False): | |
| exp24 = gr.Markdown() | |
| # btn23 = gr.Button("🔄 Generar Nube de Palabras", variant="primary") | |
| df_tx_iniciativas.change( | |
| fn=tab_viz23, | |
| inputs=[df_tx_iniciativas, gr.State('iniciativa')], | |
| outputs=[img24_pmt, exp24] | |
| ) | |
| with gr.Row(visible=False): | |
| def update_choices(listado): | |
| return gr.update(choices=listado) | |
| categorias_mass_state_pmt = gr.State(value=[]) # Estado para almacenar categorías disponibles | |
| iniciativas_pmt = gr.State(value=[]) | |
| categorias_mass_pmt = gr.Dropdown( | |
| choices=categorias_mass_state_pmt.value, multiselect=False, label="Categorías"#, info="Selecciona una categoria para aplicar el filtro." | |
| ) | |
| categorias_mass_state_pmt.change(fn=update_choices, inputs=[categorias_mass_state_pmt], outputs=[categorias_mass_pmt]) | |
| def listar_elementos_categoria(file, categoria): | |
| if file is None: # or categoria not in file.columns: | |
| return "No hay datos disponibles" | |
| df = pd.read_excel(file) | |
| if categoria not in df.columns: | |
| return f"No se encontró la categoría '{categoria}' en el archivo" | |
| elementos = df[categoria].dropna().unique().tolist() | |
| # return ", ".join(elementos) | |
| return gr.update(choices=elementos) | |
| def listar_iniciativas(file): | |
| if file is None: | |
| return [] | |
| try: | |
| df = pd.read_excel(file) | |
| if 'iniciativa' not in df.columns: | |
| return [] | |
| elementos = df['iniciativa'].dropna().unique().tolist() | |
| print(f"✅ Iniciativas cargadas: {len(elementos)}") | |
| return elementos | |
| except Exception as e: | |
| print(f"❌ Error al cargar iniciativas: {e}") | |
| return [] | |
| # filtro_categoria_state = gr.State(value=[]) # Estado para almacenar categorías seleccionadas para filtro | |
| filtro_categoria_pmt= gr.Dropdown( | |
| choices=[], multiselect=True, interactive=True, label="Elementos de categoría", info="Selecciona una o varias opciones para aplicar el análisis." | |
| ) | |
| file_output.change(listar_iniciativas, | |
| file_output, | |
| iniciativas_pmt) | |
| bttn_filtro_categoria_pmt = gr.Button("Aplicar filtro por categoría", variant="secondary") | |
| categorias_mass_pmt.change(fn=listar_elementos_categoria, inputs=[file_output, categorias_mass_pmt], outputs=[filtro_categoria_pmt]) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| with gr.Tab("TOP ODS"): | |
| html_inicio_mass_pmt = gr.HTML(label="Resultados del análisis masivo - vista inicial") | |
| with gr.Tab("Resultados por ODS - Metas"): | |
| base_ods_mass_mix_pmt = gr.Dataframe(label="Prueba-Resultados del análisis masivo ODS", visible=False) | |
| base_meta_mass_mix_pmt = gr.Dataframe(label="Resultados del análisis masivo METAS", visible=False) | |
| base_indicadores_mass_mix_pmt = gr.Dataframe(label="Resultados del análisis masivo INDICADORES", visible=False) | |
| base_ods_mass_mix_cat_pmt = gr.Dataframe(label="Prueba-Resultados del análisis masivo ODS", visible=False) | |
| base_meta_mass_mix_cat_pmt = gr.Dataframe(label="Resultados del análisis masivo METAS", visible=False) | |
| base_indicadores_mass_mix_cat_pmt = gr.Dataframe(label="Resultados del análisis masivo INDICADORES", visible=False) | |
| # output_ods_mass_mix_cat_pmt = gr.Dataframe(label="Resultados del análisis masivo ODS - Categorización", visible=False) | |
| # output_meta_mass_mix_cat_pmt = gr.Dataframe(label="Resultados del análisis masivo METAS - Categorización", visible=False) | |
| # output_indicadores_mass_mix_cat_pmt = gr.Dataframe(label="Resultados del análisis masivo INDICADORES - Categorización", visible=False) | |
| with gr.Row(): | |
| with gr.Tab("🏆 Ranking Masivo ODS"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_ods_pmt = gr.Plot(label="Ranking Global ODS") | |
| with gr.Column(scale=1): | |
| exp_mass_ods = gr.Markdown() | |
| with gr.Tab('Tabla score ODS'): | |
| output_ods_mass_mix_pmt = gr.Dataframe(label="Resultados del análisis masivo ODS") | |
| with gr.Row(): | |
| with gr.Tab("🏆 Ranking Masivo METAS"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_metas_pmt = gr.Plot(label="Ranking Global METAS") | |
| with gr.Column(scale=1): | |
| exp_mass_metas = gr.Markdown() | |
| with gr.Tab('Tabla score METAS'): | |
| output_meta_mass_mix_pmt = gr.Dataframe(label="Resultados del análisis masivo METAS") | |
| with gr.Row(visible=False): | |
| with gr.Tab("🏆 Ranking Masivo INDICADORES"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_indicadores_pmt = gr.Plot(label="Ranking Global INDICADORES") | |
| with gr.Column(scale=1): | |
| exp_mass_indicadores = gr.Markdown() | |
| with gr.Tab('Tabla score INDICADORES'): | |
| output_indicadores_mass_mix_pmt = gr.Dataframe(label="Resultados del análisis masivo INDICADORES") | |
| # Primer seguimiento de actualización | |
| with gr.Column(): | |
| output_ods_mass_mix_pmt.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix_pmt,output_meta_mass_mix_pmt,output_indicadores_mass_mix_pmt], | |
| outputs=[html_inicio_mass_pmt] | |
| ) | |
| output_meta_mass_mix_pmt.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix_pmt,output_meta_mass_mix_pmt,output_indicadores_mass_mix_pmt], | |
| outputs=[html_inicio_mass_pmt] | |
| ) | |
| output_indicadores_mass_mix_pmt.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix_pmt,output_meta_mass_mix_pmt,output_indicadores_mass_mix_pmt], | |
| outputs=[html_inicio_mass_pmt] | |
| ) | |
| with gr.Column(): | |
| output_ods_mass_mix_pmt.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_ods_mass_mix_pmt, gr.State('ods')], | |
| outputs=[plot_mass_ods_pmt, exp_mass_ods] | |
| ) | |
| output_meta_mass_mix_pmt.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_meta_mass_mix_pmt, gr.State('meta')], | |
| outputs=[plot_mass_metas_pmt, exp_mass_metas] | |
| ) | |
| output_indicadores_mass_mix_pmt.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_indicadores_mass_mix_pmt, gr.State('indicador')], | |
| outputs=[plot_mass_indicadores_pmt, exp_mass_indicadores] | |
| ) | |
| ## AJUSTE POR FILTROS DE CATEGORIA | |
| # with gr.Column(): | |
| # bttn_filtro_categoria_pmt.click( | |
| # fn=lambda df, categoria, elementos: df[df[categoria].isin(elementos)] if df is not None else None, | |
| # inputs=[base_ods_mass_mix, categorias_mass, filtro_categoria], | |
| # outputs=[base_ods_mass_mix_cat] | |
| # ) | |
| # bttn_filtro_categoria_pmt.click( | |
| # fn=lambda df, categoria, elementos: df[df[categoria].isin(elementos)] if df is not None else None, | |
| # inputs=[base_meta_mass_mix, categorias_mass, filtro_categoria], | |
| # outputs=[base_meta_mass_mix_cat] | |
| # ) | |
| # bttn_filtro_categoria_pmt.click( | |
| # fn=lambda df, categoria, elementos: df[df[categoria].isin(elementos)] if df is not None else None, | |
| # inputs=[base_indicadores_mass_mix, categorias_mass, filtro_categoria], | |
| # outputs=[base_indicadores_mass_mix_cat] | |
| # ) | |
| # base_ods_mass_mix_cat_pmt.change( | |
| # fn=tab_viz19, | |
| # inputs=[base_ods_mass_mix_cat, base_meta_mass_mix_cat, base_indicadores_mass_mix_cat], | |
| # outputs=[html19_1, html19_2, html19_3, exp19] | |
| # ) | |
| # base_meta_mass_mix_cat_pmt.change( | |
| # fn=tab_viz19, | |
| # inputs=[base_ods_mass_mix_cat_pmt, base_meta_mass_mix_cat_pmt, base_indicadores_mass_mix_cat_pmt], | |
| # outputs=[html19_1_pmt, html19_2_pmt, html19_3_pmt, exp19] | |
| # ) | |
| # base_indicadores_mass_mix_cat_pmt.change( | |
| # fn=tab_viz19, | |
| # inputs=[base_ods_mass_mix_cat_pmt, base_meta_mass_mix_cat_pmt, base_indicadores_mass_mix_cat_pmt], | |
| # outputs=[html19_1_pmt, html19_2_pmt, html19_3_pmt, exp19] | |
| # ) | |
| # btn_mass_emb.click( | |
| # fn=procesar_archivo, | |
| # inputs=[file_output], | |
| # outputs=[output_ods_mass, output_meta_mass, output_indicadores_mass,categorias_mass_state, msj_procesamiento_mass], | |
| # show_progress=True | |
| # ) | |
| btn_mass_emb_pmt.click( | |
| fn=procesar_lote_llm, | |
| inputs=[iniciativas_pmt], | |
| outputs=[output_ods_mass_mix_pmt, output_meta_mass_mix_pmt, output_indicadores_mass_mix_pmt, msj_procesamiento_mass_pmt, n_ini_state], | |
| show_progress=True | |
| ) | |
| txt_fecha_mass = gr.Textbox(value="Fecha de consulta: ", interactive=False, show_label=False) | |
| output_ods_mass_mix_pmt.change(fn= actualizar_fecha, inputs=[], outputs=txt_fecha_mass) | |
| with gr.Tab("MASIVA CON CONTEXTO", visible=False): | |
| # DataFrame vacío con estructura de iniciativas | |
| df_iniciativas_template = pd.DataFrame(columns=['id_unico', 'iniciativa', 'ODS_ID', 'META_ID', 'INDICADOR_ID', 'objetivo', 'ods_rank']) | |
| with gr.Column(): | |
| btn_mass_emb = gr.Button("Procesar archivo") | |
| msj_procesamiento_mass = gr.Textbox(value="... preparando para procesar documento", interactive=False, show_label=False) | |
| # PESTAÑA 19: RESUMEN EN TAGS | |
| # with gr.Tab("🏷️ 19. Resumen Tags"): | |
| with gr.Row(): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| html19_1 = gr.HTML(label="Resumen en Tags") | |
| with gr.Column(scale=4): | |
| # PESTAÑA 23: NUBE DE PALABRAS - PALOMA | |
| # with gr.Tab("23. Nube Palabras"): | |
| # file_output = gr.File() | |
| file_output.change(leer_tabla, | |
| file_output, | |
| df_tx_iniciativas) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| img23 = gr.Image(label="Nube de Palabras", type="filepath") | |
| with gr.Column(scale=1, visible=False): | |
| exp23 = gr.Markdown() | |
| # btn23 = gr.Button("🔄 Generar Nube de Palabras", variant="primary") | |
| df_tx_iniciativas.change( | |
| fn=tab_viz23, | |
| inputs=[df_tx_iniciativas, gr.State('iniciativa')], | |
| outputs=[img23, exp23] | |
| ) | |
| # gr.Markdown(""" | |
| # ### 📝 Nota: | |
| # Esta visualización analiza el texto de ODS/Metas/Indicadores y muestra | |
| # las palabras más frecuentes en forma de paloma de paz. | |
| # **Requiere:** `pip install wordcloud pillow` | |
| # """) | |
| with gr.Row(visible=False): | |
| html19_2 = gr.HTML(label="Resumen en Tags") | |
| # with gr.Column(scale=4): | |
| html19_3 = gr.HTML(label="Resumen en Tags") | |
| with gr.Column(visible=False): | |
| exp19 = gr.Markdown() | |
| with gr.Row(): | |
| def update_choices(listado): | |
| return gr.update(choices=listado) | |
| categorias_mass_state = gr.State(value=[]) # Estado para almacenar categorías disponibles | |
| categorias_mass = gr.Dropdown( | |
| choices=categorias_mass_state.value, multiselect=False, label="Categorías"#, info="Selecciona una categoria para aplicar el filtro." | |
| ) | |
| categorias_mass_state.change(fn=update_choices, inputs=[categorias_mass_state], outputs=[categorias_mass]) | |
| def listar_elementos_categoria(file, categoria): | |
| if file is None: # or categoria not in file.columns: | |
| return "No hay datos disponibles" | |
| df = pd.read_excel(file) | |
| if categoria not in df.columns: | |
| return f"No se encontró la categoría '{categoria}' en el archivo" | |
| elementos = df[categoria].dropna().unique().tolist() | |
| # return ", ".join(elementos) | |
| return gr.update(choices=elementos) | |
| # filtro_categoria_state = gr.State(value=[]) # Estado para almacenar categorías seleccionadas para filtro | |
| filtro_categoria= gr.Dropdown( | |
| choices=[], multiselect=True, interactive=True, label="Elementos de categoría", info="Selecciona una o varias opciones para aplicar el análisis." | |
| ) | |
| bttn_filtro_categoria = gr.Button("Aplicar filtro por categoría", variant="secondary") | |
| categorias_mass.change(fn=listar_elementos_categoria, inputs=[file_output, categorias_mass], outputs=[filtro_categoria]) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| with gr.Tab("TOP ODS"): | |
| html_inicio_mass = gr.HTML(label="Resultados del análisis masivo - vista inicial") | |
| with gr.Tab("Resultados por ODS - Metas"): | |
| base_ods_mass_mix = gr.Dataframe(label="Prueba-Resultados del análisis masivo ODS", visible=False) | |
| base_meta_mass_mix = gr.Dataframe(label="Resultados del análisis masivo METAS", visible=False) | |
| base_indicadores_mass_mix = gr.Dataframe(label="Resultados del análisis masivo INDICADORES", visible=False) | |
| base_ods_mass_mix_cat = gr.Dataframe(label="Prueba-Resultados del análisis masivo ODS", visible=False) | |
| base_meta_mass_mix_cat = gr.Dataframe(label="Resultados del análisis masivo METAS", visible=False) | |
| base_indicadores_mass_mix_cat = gr.Dataframe(label="Resultados del análisis masivo INDICADORES", visible=False) | |
| output_ods_mass_mix_cat = gr.Dataframe(label="Resultados del análisis masivo ODS - Categorización", visible=False) | |
| output_meta_mass_mix_cat = gr.Dataframe(label="Resultados del análisis masivo METAS - Categorización", visible=False) | |
| output_indicadores_mass_mix_cat = gr.Dataframe(label="Resultados del análisis masivo INDICADORES - Categorización", visible=False) | |
| with gr.Row(): | |
| with gr.Tab("🏆 Ranking Masivo ODS"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_ods = gr.Plot(label="Ranking Global ODS") | |
| with gr.Column(scale=1): | |
| exp_mass_ods = gr.Markdown() | |
| with gr.Tab("📊 Priorización con Pareto", visible=False): | |
| gr.HTML(""" | |
| <div style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #F0F0F0;">📊 Diagrama de Pareto (Regla 80/20)</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>CRITERIO:</strong> Aplicamos un criterio de Pareto ADAPTATIVO calibrado para contextos de clasificación multinivel, | |
| donde el umbral se ajusta según la distribución empírica de similaridad y la granularidad requerida para toma de decisiones.</li> | |
| <li><strong>Marcos globales:</strong> (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS | |
| prioritarios por ciclo de implementación.</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones son dinámicas y permiten exploración</li> | |
| </ol> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plotOds_mass = gr.Plot(label="Diagrama de Pareto Agregado por ODS") | |
| with gr.Column(scale=1): | |
| slider_ods_pareto_mass = gr.Slider(0.1, 1.0, step=0.1, value=0.7,interactive=True, label="Top ODS") | |
| expOds_mass = gr.Markdown() | |
| with gr.Tab('Tabla score ODS'): | |
| output_ods_mass_mix = gr.Dataframe(label="Resultados del análisis masivo ODS") | |
| with gr.Row(): | |
| with gr.Tab("🏆 Ranking Masivo METAS"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_metas = gr.Plot(label="Ranking Global METAS") | |
| with gr.Column(scale=1): | |
| exp_mass_metas = gr.Markdown() | |
| with gr.Tab("📊 Priorización con Pareto", visible=False): | |
| gr.HTML(""" | |
| <div style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #F0F0F0;">📊 Diagrama de Pareto (Regla 80/20)</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>CRITERIO:</strong> Aplicamos un criterio de Pareto ADAPTATIVO calibrado para contextos de clasificación multinivel, | |
| donde el umbral se ajusta según la distribución empírica de similaridad y la granularidad requerida para toma de decisiones.</li> | |
| <li><strong>Marcos globales:</strong> (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS | |
| prioritarios por ciclo de implementación.</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones son dinámicas y permiten exploración</li> | |
| </ol> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plotMetas_mass = gr.Plot(label="Diagrama de Pareto Agregado por METAS") | |
| with gr.Column(scale=1): | |
| slider_metas_pareto_mass = gr.Slider(0.1, 1.0, step=0.1, value=0.7,interactive=True, label="Top METAS") | |
| expMetas_mass = gr.Markdown() | |
| with gr.Tab('Tabla score METAS'): | |
| output_meta_mass_mix = gr.Dataframe(label="Resultados del análisis masivo METAS") | |
| with gr.Row(visible=False): | |
| with gr.Tab("🏆 Ranking Masivo INDICADORES"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot_mass_indicadores = gr.Plot(label="Ranking Global INDICADORES") | |
| with gr.Column(scale=1): | |
| exp_mass_indicadores = gr.Markdown() | |
| with gr.Tab("📊 Priorización con Pareto"): | |
| gr.HTML(""" | |
| <div style="background-color: #009EDB; padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: #F0F0F0;">📊 Diagrama de Pareto (Regla 80/20)</h3> | |
| <ol style="line-height: 1.8;"> | |
| <li><strong>CRITERIO:</strong> Aplicamos un criterio de Pareto ADAPTATIVO calibrado para contextos de clasificación multinivel, | |
| donde el umbral se ajusta según la distribución empírica de similaridad y la granularidad requerida para toma de decisiones.</li> | |
| <li><strong>Marcos globales:</strong> (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS | |
| prioritarios por ciclo de implementación.</li> | |
| <li><strong>Interactúa:</strong> Las visualizaciones son dinámicas y permiten exploración</li> | |
| </ol> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plotIndicadores_mass = gr.Plot(label="Diagrama de Pareto Agregado por INDICADORES") | |
| with gr.Column(scale=1): | |
| slider_indicadores_pareto_mass = gr.Slider(0.1, 1.0, step=0.1, value=0.7,interactive=True, label="Top INDICADORES") | |
| expIndicadores_mass = gr.Markdown() | |
| with gr.Tab('Tabla score INDICADORES'): | |
| output_indicadores_mass_mix = gr.Dataframe(label="Resultados del análisis masivo INDICADORES") | |
| with gr.Column(scale=1, visible=False): | |
| # u.upload(fn=upload_file, inputs=[u], outputs=[file_output]) | |
| # categorias = gr.State(value=[]) | |
| with gr.Row(): | |
| output_ods_mass = gr.Dataframe(label="Resultados del análisis masivo ODS", visible=False) | |
| output_meta_mass = gr.Dataframe(label="Resultados del análisis masivo Metas", visible=False) | |
| output_indicadores_mass = gr.Dataframe(label="Resultados del análisis masivo Indicadores", visible=False) | |
| ## Opteniendo | |
| with gr.Row(): | |
| with gr.Column(): | |
| slider_pareto_ods = gr.Slider(0, 1, step=0.05, value=0.40,interactive=True, label="Umbral Pareto ODS / iniciativa") | |
| slider_pareto_meta = gr.Slider(0, 1, step=0.05, value=0.10,interactive=True, label="Umbral Pareto Metas / iniciativa") | |
| slider_pareto_indicador = gr.Slider(0, 1, step=0.05, value=0.10,interactive=True, label="Umbral Pareto Indicadores / iniciativa") | |
| with gr.Column(): | |
| slider_prop_sim = gr.Slider(0, 1, step=0.05, value=0.50,interactive=True, label="Proporción calificación Similaridad") | |
| slider_prop_rank = gr.Slider(0, 1, step=0.05, value=0.35,interactive=True, label="Proporción calificación Ranking") | |
| # prop_frec = gr.State(value=0.20) # 1 - sim - rank | |
| ## Actualización dinámica de proporciones para que siempre sumen 1 | |
| slider_prop_sim.change(fn=lambda sim: gr.update(value=round(0, 2)) if sim is not None else None, inputs=[slider_prop_sim], outputs=[slider_prop_rank]) | |
| slider_prop_sim.change(fn=lambda sim: gr.update(maximum=round(1-sim, 2), value=round(0, 2)) if sim is not None else None, inputs=[slider_prop_sim], outputs=[slider_prop_rank]) | |
| # slider_prop_rank.change(fn=lambda sim, rank: gr.update(value=1-(rank + sim)) if rank is not None else None, inputs=[slider_prop_sim, slider_prop_rank], outputs=[prop_frec]) | |
| with gr.Row(): | |
| # with gr.Column(scale=2): | |
| with gr.Tab("Composición del Score"): | |
| plot22 = gr.Plot(label="Composición del Score") | |
| # with gr.Column(scale=1): | |
| with gr.Tab("Detalle de scores"): | |
| exp22 = gr.Markdown() | |
| # btn22 = gr.Button("🔄 Mostrar Composición", variant="primary") | |
| slider_prop_sim.change( | |
| fn=tab_viz22, | |
| inputs=[slider_prop_sim, slider_prop_rank], | |
| outputs=[plot22, exp22] | |
| ) | |
| slider_prop_rank.change( | |
| fn=tab_viz22, | |
| inputs=[slider_prop_sim, slider_prop_rank], | |
| outputs=[plot22, exp22] | |
| ) | |
| btn_mass_emb.click( | |
| fn=procesar_archivo, | |
| inputs=[file_output], | |
| outputs=[output_ods_mass, output_meta_mass, output_indicadores_mass,categorias_mass_state, msj_procesamiento_mass], | |
| show_progress=True | |
| ) | |
| with gr.Row(): ## Actualizando todas las variables de análisis cada vez que se ajusta un slider o se carga un nuevo archivo, para mantener todo sincronizado | |
| with gr.Column(): | |
| output_ods_mass.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_ods_mass, gr.State('ods'),slider_pareto_ods, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_ods_mass_mix, base_ods_mass_mix] | |
| ) | |
| output_meta_mass.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_meta_mass, gr.State('meta'), slider_pareto_meta, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_meta_mass_mix, base_meta_mass_mix] | |
| ) | |
| output_indicadores_mass.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_indicadores_mass, gr.State('indicador'), slider_pareto_indicador, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_indicadores_mass_mix, base_indicadores_mass_mix] | |
| ) | |
| with gr.Column(): | |
| output_ods_mass_mix.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix,output_meta_mass_mix,output_indicadores_mass_mix], | |
| outputs=[html_inicio_mass] | |
| ) | |
| output_meta_mass_mix.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix,output_meta_mass_mix,output_indicadores_mass_mix], | |
| outputs=[html_inicio_mass] | |
| ) | |
| output_indicadores_mass_mix.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix,output_meta_mass_mix,output_indicadores_mass_mix], | |
| outputs=[html_inicio_mass] | |
| ) | |
| with gr.Column(): | |
| slider_prop_sim.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_ods_mass, gr.State('ods'), slider_pareto_ods, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_ods_mass_mix, base_ods_mass_mix] | |
| ) | |
| slider_prop_sim.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_meta_mass, gr.State('meta'), slider_pareto_meta, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_meta_mass_mix, base_meta_mass_mix] | |
| ) | |
| slider_prop_sim.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_indicadores_mass, gr.State('indicador'), slider_pareto_indicador, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_indicadores_mass_mix, base_indicadores_mass_mix ] | |
| ) | |
| slider_prop_rank.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_ods_mass, gr.State('ods'), slider_pareto_ods, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_ods_mass_mix, base_ods_mass_mix] | |
| ) | |
| slider_prop_rank.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_meta_mass, gr.State('meta'), slider_pareto_meta, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_meta_mass_mix, base_meta_mass_mix ] | |
| ) | |
| slider_prop_rank.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_indicadores_mass, gr.State('indicador'), slider_pareto_indicador, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_indicadores_mass_mix, base_indicadores_mass_mix ] | |
| ) | |
| with gr.Column(): | |
| slider_pareto_ods.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_ods_mass, gr.State('ods'), slider_pareto_ods, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_ods_mass_mix, base_ods_mass_mix] | |
| ) | |
| slider_pareto_meta.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_meta_mass, gr.State('meta'), slider_pareto_meta, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_meta_mass_mix, base_meta_mass_mix ] | |
| ) | |
| slider_pareto_indicador.change( | |
| fn=analisis_global_con_pareto, | |
| inputs=[output_indicadores_mass, gr.State('indicador'), slider_pareto_indicador, gr.State('mixto'), slider_prop_sim, slider_prop_rank, gr.State(False)], | |
| outputs=[output_indicadores_mass_mix, base_indicadores_mass_mix] | |
| ) | |
| with gr.Column(): | |
| output_ods_mass_mix.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_ods_mass_mix, gr.State('ods'), slider_pareto_ods, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_ods, exp_mass_ods] | |
| ) | |
| output_meta_mass_mix.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_meta_mass_mix, gr.State('meta'), slider_pareto_meta, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_metas, exp_mass_metas] | |
| ) | |
| output_indicadores_mass_mix.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_indicadores_mass_mix, gr.State('indicador'), slider_pareto_indicador, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_indicadores, exp_mass_indicadores] | |
| ) | |
| with gr.Column(): # actualizar grafico barras de puntajes cada vez que se ajusta proporción de similaridad o ranking, para mantener todo sincronizado | |
| slider_prop_sim.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_ods_mass_mix, gr.State('ods'), slider_pareto_meta, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_metas, exp_mass_metas] | |
| ) | |
| slider_prop_rank.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_ods_mass_mix, gr.State('ods'), slider_pareto_indicador, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_indicadores, exp_mass_indicadores] | |
| ) | |
| slider_prop_sim.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_meta_mass_mix, gr.State('meta'), slider_pareto_meta, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_metas, exp_mass_metas] | |
| ) | |
| slider_prop_rank.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_meta_mass_mix, gr.State('meta'), slider_pareto_indicador, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_indicadores, exp_mass_indicadores] | |
| ) | |
| slider_prop_sim.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_indicadores_mass_mix, gr.State('indicador'), slider_pareto_meta, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_metas, exp_mass_metas] | |
| ) | |
| slider_prop_rank.change( | |
| fn=tab_viz21, | |
| #df, nivel, umbral_pareto=0.8, metodo='mixto', sim_prop = 0.75, rank_prop=0.20 | |
| inputs=[output_indicadores_mass_mix, gr.State('indicador'), slider_pareto_indicador, gr.State(value='mixto'), slider_prop_sim, slider_prop_rank], | |
| outputs=[plot_mass_indicadores, exp_mass_indicadores] | |
| ) | |
| with gr.Column(): | |
| output_ods_mass_mix.change( | |
| fn=tab_viz20, | |
| inputs=[output_ods_mass_mix, gr.State('ods'), slider_ods_pareto_mass, gr.State(True), gr.State('individual')], | |
| outputs=[plotOds_mass, expOds_mass] | |
| ) | |
| output_meta_mass_mix.change( | |
| fn=tab_viz20, | |
| inputs=[output_meta_mass_mix, gr.State('meta'), slider_metas_pareto_mass, gr.State(True), gr.State('individual')], | |
| outputs=[plotMetas_mass, expMetas_mass] | |
| ) | |
| output_indicadores_mass_mix.change( | |
| fn=tab_viz20, | |
| inputs=[output_indicadores_mass_mix, gr.State('indicador'), slider_indicadores_pareto_mass, gr.State(True), gr.State('individual')], | |
| outputs=[plotIndicadores_mass, expIndicadores_mass] | |
| ) | |
| ### Actualizando umbral de pareto | |
| slider_ods_pareto_mass.change( | |
| fn=tab_viz20, | |
| inputs=[output_ods_mass_mix, gr.State('ods'), slider_ods_pareto_mass, gr.State(True), gr.State('individual')], | |
| outputs=[plotOds_mass, expOds_mass] | |
| ) | |
| slider_metas_pareto_mass.change( | |
| fn=tab_viz20, | |
| inputs=[output_meta_mass_mix, gr.State('meta'), slider_metas_pareto_mass, gr.State(True), gr.State('individual')], | |
| outputs=[plotMetas_mass, expMetas_mass] | |
| ) | |
| slider_indicadores_pareto_mass.change( | |
| fn=tab_viz20, | |
| inputs=[output_indicadores_mass_mix, gr.State('indicador'), slider_indicadores_pareto_mass, gr.State(True), gr.State('individual')], | |
| outputs=[plotIndicadores_mass, expIndicadores_mass] | |
| ) | |
| with gr.Column(): | |
| # btn19 = gr.Button("🔄 Generar Resumen", variant="primary") | |
| base_ods_mass_mix.change( | |
| fn=tab_viz19, | |
| inputs=[base_ods_mass_mix, base_meta_mass_mix, base_indicadores_mass_mix], | |
| outputs=[html19_1, html19_2, html19_3, exp19] | |
| ) | |
| base_meta_mass_mix.change( | |
| fn=tab_viz19, | |
| inputs=[base_ods_mass_mix, base_meta_mass_mix, base_indicadores_mass_mix], | |
| outputs=[html19_1, html19_2, html19_3, exp19] | |
| ) | |
| base_indicadores_mass_mix.change( | |
| fn=tab_viz19, | |
| inputs=[base_ods_mass_mix, base_meta_mass_mix, base_indicadores_mass_mix], | |
| outputs=[html19_1, html19_2, html19_3, exp19] | |
| ) | |
| ## AJUSTE POR FILTROS DE CATEGORIA | |
| with gr.Column(): | |
| bttn_filtro_categoria.click( | |
| fn=lambda df, categoria, elementos: df[df[categoria].isin(elementos)] if df is not None else None, | |
| inputs=[base_ods_mass_mix, categorias_mass, filtro_categoria], | |
| outputs=[base_ods_mass_mix_cat] | |
| ) | |
| bttn_filtro_categoria.click( | |
| fn=lambda df, categoria, elementos: df[df[categoria].isin(elementos)] if df is not None else None, | |
| inputs=[base_meta_mass_mix, categorias_mass, filtro_categoria], | |
| outputs=[base_meta_mass_mix_cat] | |
| ) | |
| bttn_filtro_categoria.click( | |
| fn=lambda df, categoria, elementos: df[df[categoria].isin(elementos)] if df is not None else None, | |
| inputs=[base_indicadores_mass_mix, categorias_mass, filtro_categoria], | |
| outputs=[base_indicadores_mass_mix_cat] | |
| ) | |
| base_ods_mass_mix_cat.change( | |
| fn=tab_viz19, | |
| inputs=[base_ods_mass_mix_cat, base_meta_mass_mix_cat, base_indicadores_mass_mix_cat], | |
| outputs=[html19_1, html19_2, html19_3, exp19] | |
| ) | |
| base_meta_mass_mix_cat.change( | |
| fn=tab_viz19, | |
| inputs=[base_ods_mass_mix_cat, base_meta_mass_mix_cat, base_indicadores_mass_mix_cat], | |
| outputs=[html19_1, html19_2, html19_3, exp19] | |
| ) | |
| base_indicadores_mass_mix_cat.change( | |
| fn=tab_viz19, | |
| inputs=[base_ods_mass_mix_cat, base_meta_mass_mix_cat, base_indicadores_mass_mix_cat], | |
| outputs=[html19_1, html19_2, html19_3, exp19] | |
| ) | |
| ## Actualilzar resultados de análisis cada vez que se ajusta el filtro de categoría, para mantener todo sincronizado | |
| def actualizar_analisis_con_filtro_categoria(df, categoria, elementos, nivel): | |
| if df is None: | |
| return None | |
| df_filtrado = df[df[categoria].isin(elementos)] | |
| if nivel == 'ods': | |
| umbral_pareto = slider_pareto_ods.value | |
| elif nivel == 'meta': | |
| umbral_pareto = slider_pareto_meta.value | |
| elif nivel == 'indicador': | |
| umbral_pareto = slider_pareto_indicador.value | |
| else: | |
| umbral_pareto = 0.8 # valor por defecto | |
| resultado_actualizado, base_actualizada = analisis_global_con_pareto(df_filtrado, nivel, umbral_pareto, metodo='mixto', sim_prop=slider_prop_sim.value, rank_prop=slider_prop_rank.value) | |
| return resultado_actualizado, base_actualizada | |
| bttn_filtro_categoria.click( | |
| fn=actualizar_analisis_con_filtro_categoria, | |
| inputs=[output_ods_mass, categorias_mass, filtro_categoria, gr.State('ods')], | |
| outputs=[output_ods_mass_mix_cat, base_ods_mass_mix_cat] | |
| ) | |
| bttn_filtro_categoria.click( | |
| fn=actualizar_analisis_con_filtro_categoria, | |
| inputs=[output_meta_mass, categorias_mass, filtro_categoria, gr.State('meta')], | |
| outputs=[output_meta_mass_mix_cat, base_meta_mass_mix_cat] | |
| ) | |
| bttn_filtro_categoria.click( | |
| fn=actualizar_analisis_con_filtro_categoria, | |
| inputs=[output_indicadores_mass, categorias_mass, filtro_categoria, gr.State('indicador')], | |
| outputs=[output_indicadores_mass_mix_cat, base_indicadores_mass_mix_cat] | |
| ) | |
| ## Actuaizar visualizaciones niveles cada vez que se ajusta el filtro de categoría, para mantener todo sincronizado | |
| output_ods_mass_mix_cat.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix_cat,output_meta_mass_mix_cat,output_indicadores_mass_mix_cat], | |
| outputs=[html_inicio_mass] | |
| ) | |
| output_meta_mass_mix_cat.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix_cat,output_meta_mass_mix_cat,output_indicadores_mass_mix_cat], | |
| outputs=[html_inicio_mass] | |
| ) | |
| output_indicadores_mass_mix_cat.change( | |
| fn=tab_inicio_mass, | |
| inputs=[output_ods_mass_mix_cat,output_meta_mass_mix_cat,output_indicadores_mass_mix_cat], | |
| outputs=[html_inicio_mass] | |
| ) | |
| # categorias.change(fn=lambda df, n: df[df[f'ods_peso_acumulado_sin_norm'] <= 1-n] if df is not None else None, | |
| # inputs=[output_ods_mass_mix, categorias], | |
| # outputs=[output_ods_mass_mix], api_name="ods") | |
| with gr.Tab("Análisis 2 - Filtrado", visible=False): | |
| with gr.Row(): | |
| with gr.Column(): | |
| slider_massive_ods_sim = gr.Slider(0, 1, step=0.01, value=0.70,interactive=True, label="Sensibilidad ODS") | |
| slider_massive_ods_rank = gr.Slider(0, 10, step=1, value=4,interactive=True, label="Top ODS") | |
| with gr.Column(): | |
| slider_massive_meta_sim = gr.Slider(0, 1, step=0.01, value=0.70,interactive=True, label="Sensibilidad METAS") | |
| slider_massive_meta_rank = gr.Slider(0, 10, step=1, value=4,interactive=True, label="Top METAS") | |
| with gr.Column(): | |
| slider_massive_indicador_sim = gr.Slider(0, 1, step=0.01, value=0.70,interactive=True, label="Sensibilidad INDICADORES") | |
| slider_massive_indicador_rank = gr.Slider(0, 10, step=1, value=4,interactive=True, label="Top INDICADORES") | |
| with gr.Tab("Tablas Filtradas", visible=True): | |
| with gr.Column(): | |
| df_ods_mass_filtrado = gr.Dataframe(df_iniciativas_template, label="METAS Filtradas") | |
| slider_massive_ods_sim.release(fn=lambda df, n: df[df[f'ods_peso_acumulado_sin_norm'] <= 1-n] if df is not None else None, | |
| inputs=[output_ods_mass, slider_massive_ods_sim], | |
| outputs=[df_ods_mass_filtrado], api_name="ods") | |
| slider_massive_ods_rank.release(fn=lambda df, n: df[df[f'ods_rank'] <= n] if df is not None else None, | |
| inputs=[output_ods_mass, slider_massive_ods_rank], | |
| outputs=[df_ods_mass_filtrado], api_name="ods") | |
| df_meta_mass_filtrado = gr.Dataframe(df_iniciativas_template, label="METAS Filtradas") | |
| slider_massive_meta_sim.release(fn=lambda df, n: df[df[f'meta_peso_acumulado_sin_norm'] <= 1-n] if df is not None else None, | |
| inputs=[output_meta_mass, slider_massive_meta_sim], | |
| outputs=[df_meta_mass_filtrado], api_name="ods") | |
| slider_massive_meta_rank.release(fn=lambda df, n: df[df[f'meta_rank'] <= n] if df is not None else None, | |
| inputs=[output_meta_mass, slider_massive_meta_rank], | |
| outputs=[df_meta_mass_filtrado], api_name="ods") | |
| df_indicador_mass_filtrado = gr.Dataframe(df_iniciativas_template, label="Indicadores Filtradas") | |
| slider_massive_indicador_sim.release(fn=lambda df, n: df[df[f'indicador_peso_acumulado_sin_norm'] <= 1-n] if df is not None else None, | |
| inputs=[output_indicadores_mass, slider_massive_indicador_sim], | |
| outputs=[df_indicador_mass_filtrado], api_name="ods") | |
| slider_massive_indicador_rank.release(fn=lambda df, n: df[df[f'indicador_rank'] <= n] if df is not None else None, | |
| inputs=[output_indicadores_mass, slider_massive_indicador_rank], | |
| outputs=[df_indicador_mass_filtrado], api_name="ods") | |
| # PESTAÑA Graficas | |
| with gr.Tab("🕸️ Gráficas", visible=False): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot4 = gr.Plot(label="Gráfico de Radar") | |
| with gr.Column(scale=1): | |
| exp4 = gr.Markdown() | |
| # btn4_m = gr.Button("🔄 Generar Visualización", variant="primary") | |
| df_ods_mass_filtrado.change( | |
| fn=tab_viz4, | |
| inputs=[df_ods_mass_filtrado, gr.State('ODS_ID'), gr.State('ods_peso_sin_norm'), gr.State('ods_rank'), gr.State('ODS')], | |
| outputs=[plot4, exp4] | |
| ) | |
| # with gr.Row(): | |
| # with gr.Column(scale=2): | |
| # plot6 = gr.Plot(label="Top 5 Indicadores por ODS") | |
| # with gr.Column(scale=1): | |
| # exp6 = gr.Markdown() | |
| # # btn6 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| # df_meta_mass_filtrado.change( | |
| # fn=tab_viz6, | |
| # inputs=[df_meta_mass_filtrado, gr.State('ODS_ID'), gr.State('meta_similaridad_cos'), gr.State('meta_rank'), gr.State('METAS'), gr.State(3)], | |
| # outputs=[plot6, exp6] | |
| # ) | |
| # PESTAÑA 5: SUNBURST | |
| # with gr.Tab("☀️ 5. Sunburst"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| plot5 = gr.Plot(label="Diagrama de Sol") | |
| with gr.Column(scale=1): | |
| exp5 = gr.Markdown() | |
| # btn5 = gr.Button("🔄 Generar Visualización", variant="primary") | |
| df_meta_mass_filtrado.change( | |
| fn=tab_viz5, | |
| inputs=[df_meta_mass_filtrado, gr.State('META_ID1'), gr.State('meta_rank'), gr.State('meta_rank'), gr.State('METAS')], | |
| outputs=[plot5, exp5] | |
| ) | |
| with gr.Tab("MASIVA CON PROMPTING", visible=False): | |
| gr.Markdown("Aquí puedes processar múltiples iniciativas con enfoque de prompting. (Usa la plantilla proporcionada)") | |
| # client = InferenceClient(token="hf_tu_token_aqui") | |
| # Celda 3: Prompts por nivel | |
| PROMPT_ODS = """Vincula entre 3 y 5 Objetivos de Desarrollo Sostenible (ODS) que tengan una relación directa con la siguiente iniciativa: | |
| "{iniciativa}" | |
| Para cada ODS vinculado incluye: | |
| - Número y nombre del ODS | |
| - Justificación breve de por qué se vincula (impacto social, territorial, de paz, infraestructura o inclusión) | |
| Formato de respuesta (una línea por ODS, ordenados por relevancia de mayor a menor): | |
| ODS [número]: [nombre] – [justificación] | |
| Responde ÚNICAMENTE con el listado en el formato indicado, sin texto adicional.""" | |
| PROMPT_META = """Identifica entre 3 y 5 Metas de los Objetivos de Desarrollo Sostenible (ODS) que tengan relación directa con la siguiente iniciativa: | |
| "{iniciativa}" | |
| Para cada meta incluye: | |
| - Código de la meta (ej: 4.2, 1.3, 16.10) | |
| - Descripción de la meta | |
| - Justificación breve del vínculo con la iniciativa | |
| Formato de respuesta (una línea por meta, ordenadas por relevancia de mayor a menor): | |
| Meta [código]: [descripción] – [justificación] | |
| Responde ÚNICAMENTE con el listado en el formato indicado, sin texto adicional.""" | |
| PROMPT_INDICADOR = """Identifica entre 3 y 5 Indicadores de los Objetivos de Desarrollo Sostenible (ODS) que permitan medir el impacto de la siguiente iniciativa: | |
| "{iniciativa}" | |
| Para cada indicador incluye: | |
| - Código del indicador (ej: 4.2.1, 1.3.1, 16.10.1) | |
| - Descripción del indicador | |
| - Justificación breve de cómo se relaciona con la medición de la iniciativa | |
| Formato de respuesta (una línea por indicador, ordenados por relevancia de mayor a menor): | |
| Indicador [código]: [descripción] – [justificación] | |
| Responde ÚNICAMENTE con el listado en el formato indicado, sin texto adicional.""" | |
| # Celda 4: Funciones de parsing por nivel | |
| def parsear_ods(texto: str) -> list: | |
| """Extrae ODS de la respuesta.""" | |
| items = [] | |
| for linea in texto.strip().split("\n"): | |
| linea = linea.strip() | |
| if not linea: | |
| continue | |
| # Buscar patrón ODS [número]: [nombre] – [justificación] | |
| match = re.match(r"(?:\d+\.\s*)?ODS\s*(\d+):?\s*(.+)", linea, re.IGNORECASE) | |
| if match: | |
| ods_num = match.group(1) | |
| resto = match.group(2).strip() | |
| items.append({ | |
| "ods_id": f"ODS {ods_num}", | |
| "objetivo": resto | |
| }) | |
| return items | |
| def parsear_metas(texto: str) -> list: | |
| """Extrae metas de la respuesta.""" | |
| items = [] | |
| for linea in texto.strip().split("\n"): | |
| linea = linea.strip() | |
| if not linea: | |
| continue | |
| # Buscar patrón Meta [código]: [descripción] | |
| match = re.match(r"(?:\d+\.\s*)?Meta\s*([\d\.]+):?\s*(.+)", linea, re.IGNORECASE) | |
| if match: | |
| meta_codigo = match.group(1) | |
| resto = match.group(2).strip() | |
| # Extraer ODS del código de meta | |
| ods_num = meta_codigo.split(".")[0] | |
| items.append({ | |
| "ods_id": f"ODS {ods_num}", | |
| "meta_id": f"Meta {meta_codigo}", | |
| "objetivo": resto | |
| }) | |
| return items | |
| def parsear_indicadores(texto: str) -> list: | |
| """Extrae indicadores de la respuesta.""" | |
| items = [] | |
| for linea in texto.strip().split("\n"): | |
| linea = linea.strip() | |
| if not linea: | |
| continue | |
| # Buscar patrón Indicador [código]: [descripción] | |
| match = re.match(r"(?:\d+\.\s*)?Indicador\s*([\d\.]+):?\s*(.+)", linea, re.IGNORECASE) | |
| if match: | |
| ind_codigo = match.group(1) | |
| resto = match.group(2).strip() | |
| # Extraer ODS del código | |
| ods_num = ind_codigo.split(".")[0] | |
| items.append({ | |
| "ods_id": f"ODS {ods_num}", | |
| "indicador_id": f"Indicador {ind_codigo}", | |
| "objetivo": resto | |
| }) | |
| return items | |
| # Celda 5: Función de clasificación por nivel | |
| def clasificar_nivel(iniciativa: str, nivel: str) -> dict: | |
| """Clasifica una iniciativa en un nivel específico (ods, meta, indicador).""" | |
| prompts = { | |
| "ods": PROMPT_ODS, | |
| "meta": PROMPT_META, | |
| "indicador": PROMPT_INDICADOR | |
| } | |
| parsers = { | |
| "ods": parsear_ods, | |
| "meta": parsear_metas, | |
| "indicador": parsear_indicadores | |
| } | |
| try: | |
| respuesta = client.chat_completion( | |
| model="Qwen/Qwen2.5-7B-Instruct", | |
| messages=[{"role": "user", "content": prompts[nivel].format(iniciativa=iniciativa)}], | |
| max_tokens=1000, | |
| temperature=0.3 | |
| ) | |
| texto = respuesta.choices[0].message.content | |
| items = parsers[nivel](texto) | |
| return { | |
| "iniciativa": iniciativa, | |
| "items": items, | |
| "respuesta_raw": texto, | |
| "error": None | |
| } | |
| except Exception as e: | |
| return { | |
| "iniciativa": iniciativa, | |
| "items": [], | |
| "respuesta_raw": "", | |
| "error": str(e) | |
| } | |
| # Celda 6: Función principal de procesamiento por lotes | |
| def procesar_lote(iniciativas: list, mostrar_progreso: bool = True) -> dict: | |
| """ | |
| Procesa un listado de iniciativas y genera 3 DataFrames: | |
| - df_ods: Clasificación a nivel ODS | |
| - df_metas: Clasificación a nivel Meta | |
| - df_indicadores: Clasificación a nivel Indicador | |
| """ | |
| niveles = ["ods", "meta", "indicador"] | |
| resultados = {nivel: [] for nivel in niveles} | |
| total = len(iniciativas) * len(niveles) | |
| contador = 0 | |
| for idx, iniciativa in enumerate(iniciativas): | |
| iniciativa_id = f"INI_{idx + 1:04d}" | |
| for nivel in niveles: | |
| contador += 1 | |
| if mostrar_progreso: | |
| print(f"[{contador}/{total}] {iniciativa_id} - {nivel.upper()}: {iniciativa[:50]}...") | |
| resultado = clasificar_nivel(iniciativa, nivel) | |
| if resultado["error"]: | |
| resultados[nivel].append({ | |
| "INICIATIVA_ID": iniciativa_id, | |
| "ODS_ID": "ERROR", | |
| "OBJETIVO": resultado["error"], | |
| "ods_rank": 0, | |
| "iniciativa": iniciativa | |
| }) | |
| else: | |
| for rank, item in enumerate(resultado["items"], start=1): | |
| fila = { | |
| "INICIATIVA_ID": iniciativa_id, | |
| "ODS_ID": item.get("ods_id", ""), | |
| "OBJETIVO": item.get("objetivo", ""), | |
| "ods_rank": rank, | |
| "iniciativa": iniciativa | |
| } | |
| # Agregar columna específica del nivel si aplica | |
| if nivel == "meta": | |
| fila["META_ID"] = item.get("meta_id", "") | |
| elif nivel == "indicador": | |
| fila["INDICADOR_ID"] = item.get("indicador_id", "") | |
| resultados[nivel].append(fila) | |
| time.sleep(0.5) # evitar rate limits | |
| # Crear DataFrames | |
| df_ods = pd.DataFrame(resultados["ods"]) | |
| if not df_ods.empty and "INICIATIVA_ID" in df_ods.columns: | |
| df_ods = df_ods[["INICIATIVA_ID", "ODS_ID", "OBJETIVO", "ods_rank", "iniciativa"]] | |
| df_metas = pd.DataFrame(resultados["meta"]) | |
| if not df_metas.empty and "META_ID" in df_metas.columns: | |
| df_metas = df_metas[["INICIATIVA_ID", "ODS_ID", "META_ID", "OBJETIVO", "ods_rank", "iniciativa"]] | |
| df_indicadores = pd.DataFrame(resultados["indicador"]) | |
| if not df_indicadores.empty and "INDICADOR_ID" in df_indicadores.columns: | |
| df_indicadores = df_indicadores[["INICIATIVA_ID", "ODS_ID", "INDICADOR_ID", "OBJETIVO", "ods_rank", "iniciativa"]] | |
| # return { | |
| # "df_ods": df_ods, | |
| # "df_metas": df_metas, | |
| # "df_indicadores": df_indicadores | |
| # } | |
| return df_ods, df_metas, df_indicadores | |
| def procesar_desde_archivo(ruta_archivo: str, columna_iniciativa: str = None) -> dict: | |
| """Carga iniciativas desde CSV/Excel y las procesa.""" | |
| if ruta_archivo.endswith(".csv"): | |
| df = pd.read_csv(ruta_archivo) | |
| else: | |
| df = pd.read_excel(ruta_archivo) | |
| # Detectar columna si no se especifica | |
| if columna_iniciativa is None: | |
| for col in df.columns: | |
| if any(x in col.lower() for x in ["iniciativa", "descripcion", "nombre", "proyecto"]): | |
| columna_iniciativa = col | |
| break | |
| if columna_iniciativa is None: | |
| columna_iniciativa = df.columns[0] | |
| print(f"Usando columna: {columna_iniciativa}") | |
| print(f"Total iniciativas: {len(df)}") | |
| iniciativas = df[columna_iniciativa].astype(str).tolist() | |
| return procesar_lote(iniciativas) | |
| btn_mass_prompt = gr.Button("Procesar archivo") | |
| with gr.Column(): | |
| output_ods_mass_prompt = gr.Dataframe(label="Resultados del análisis masivo ODS", visible=True, interactive=True) | |
| output_meta_mass_prompt = gr.Dataframe(label="Resultados del análisis masivo Metas", visible=True, interactive=True) | |
| output_indicadores_mass_prompt = gr.Dataframe(label="Resultados del análisis masivo Indicadores", visible=True, interactive=True) | |
| btn_mass_prompt.click( | |
| fn=procesar_desde_archivo, | |
| inputs=[file_output, gr.State('iniciativa')], | |
| outputs=[output_ods_mass_prompt, output_meta_mass_prompt, output_indicadores_mass_prompt], | |
| show_progress=True | |
| ) | |
| # --- | |
| # ### 📚 Recursos Adicionales | |
| # - **Documentación completa**: Consulta los archivos `.md` incluidos | |
| # - **Código fuente**: `visualizaciones_ods.py` | |
| # - **Documento Word**: `GUIA_VISUALIZACIONES_PUBLICO_GENERAL.docx` | |
| # --- | |
| # *Sistema de Visualización ODS | Febrero 2026 | Desarrollado con Python, Plotly, Matplotlib y Gradio* | |
| # Pie de página | |
| gr.Markdown(""" | |
| --- | |
| --- | |
| *Sistema de Visualización ODS - UNFPA | Marzo 2026 | Desarrollado con Python, Gradio y gemini-2.5-flash-lite* | |
| """) | |
| return app | |
| # ============================================================================ | |
| # EJECUCIÓN DE LA APLICACIÓN | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| print("\n" + "="*70) | |
| print("INICIANDO APLICACIÓN GRADIO - VISUALIZACIONES ODS") | |
| print("="*70) | |
| # if not DATOS_CARGADOS: | |
| # print("\n⚠️ ADVERTENCIA: No se pudieron cargar los datos.") | |
| # print(" Verifica que el archivo existe en:", RUTA_DATOS) | |
| # print(" La aplicación se iniciará pero mostrará errores.") | |
| # else: | |
| # print(f"\n✓ Datos cargados correctamente: {len(df_global)} registros") | |
| # print(f"✓ ODS únicos: {df_global['ods_id'].nunique()}") | |
| print("\n" + "="*70) | |
| print("CREANDO APLICACIÓN...") | |
| print("="*70) | |
| app = crear_app() | |
| print("\n✓ Aplicación creada exitosamente") | |
| print("\n" + "="*70) | |
| print("INICIANDO SERVIDOR WEB...") | |
| print("="*70) | |
| print("\n🌐 La aplicación se abrirá en tu navegador automáticamente") | |
| # print("📍 URL local: http://127.0.0.1:7860") | |
| print("🌍 URL pública: Se generará si share=True\n") | |
| print("💡 Presiona Ctrl+C para detener el servidor\n") | |
| # Lanzar la aplicación | |
| app.launch( | |
| # theme="light", | |
| # server_name="0.0.0.0", # Permite acceso desde cualquier IP | |
| # server_port=7860, # Puerto por defecto | |
| # share=True, # Cambiar a True para URL pública | |
| show_error=True, # Mostrar errores en la interfaz | |
| # quiet=False # Mostrar logs en consola | |
| debug=True # Modo debug para desarrollo | |
| ) | |