""" 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"""

📊 Tu texto en clave de ODS

El análisis identifica que tu texto se relaciona principalmente con estos Objetivos de Desarrollo Sostenible ODS

🏆 Top 4 ODS Más Relevantes

{''.join([f'''
ODS {row['ODS_ID']}
''' for _, row in top_ods.iterrows()])}

🎯 Metas Más Relevantes Por ODS Top

{''.join([f'''
ODS {row['ODS_ID']}

Meta: {row['META_ID']}

''' for _, row in top_metas.iterrows()])}

📋 Recomendaciones Territoriales por Meta

{''.join([f''' ''' for _, row in top_metas.iterrows()])}
ODS Meta ID Recomendaciones Territoriales
ODS {row['ODS_ID']} {row['META_ID']} {row['Recomendaciones_territoriales']}
""" 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"""

📊 Tu texto en clave de ODS

El análisis identifica que tu texto se relaciona principalmente con estos Objetivos de Desarrollo Sostenible ODS

🏆 Top 4 ODS Más Relevantes

{''.join([f'''
ODS {row['ODS_ID']}
''' for _, row in top_ods.iterrows()])}

🎯 Metas Más Relevantes Por ODS Top

{''.join([f'''
ODS {row['META_ID']}

Meta: {row['META_ID']}

''' for _, row in top_metas.iterrows()])}

📋 Acciones Territoriales asociadas a Metas ODS

{''.join([f''' ''' for _, row in top_metas.iterrows()])}
META Meta ID Desde las metas de ODS, ¿Cuáles son las acciones asociadas con la iniciativa, problemática u oportunidad en tu territorio?
ODS {row['META_ID']} {row['META_ID']} {row['Recomendaciones_territoriales']}
""" 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 "

⚠️ No hay datos ODS disponibles

" if df_metas is None or df_metas.empty: return "

⚠️ No hay datos de METAS disponibles

" # 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"""

📊 Tus textos en clave de ODS

El análisis identifica que tus textos se relacionan principalmente con estos Objetivos de Desarrollo Sostenible ODS

🏆 Top 4 ODS Más Relevantes

{''.join([f'''
ODS {row['ODS_ID']}
''' for _, row in top_ods.iterrows()])}

🎯 Metas Más Relevantes Por ODS Top

{''.join([f'''
ODS {row['ODS_ID']}

Meta: {row['META_ID']}

''' for _, row in top_metas.iterrows()])}

📋 Acciones Territoriales asociadas a Metas ODS

{''.join([f''' ''' for _, row in top_metas.iterrows()])}
ODS Meta ID Desde las metas de ODS, ¿Cuáles son las acciones asociadas con la iniciativa, problemática u oportunidad en tu territorio?
ODS {row['ODS_ID']} {row['META_ID']} {row['Recomendaciones_territoriales']}
""" 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"""

📈 Análisis Estadístico Detallado

1. Estadísticas Globales

Cantidad de datos: {stats['count']:.0f}
Media: {stats['mean']:.4f}
Desviación Estándar: {stats['std']:.4f}
Mínimo: {stats['min']:.4f}
Q1 (Percentil 25): {stats['25%']:.4f}
Mediana (Q2): {stats['50%']:.4f}
Q3 (Percentil 75): {stats['75%']:.4f}
Máximo: {stats['max']:.4f}

2. Validación del Sistema

Correlación Rank vs Similaridad: {correlacion:.4f}

Interpretación: { "✅ 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" }

Una correlación negativa fuerte indica que a mayor ranking (menos relevante), menor es la similaridad, lo cual es el comportamiento esperado.

3. Estadísticas por ODS

{''.join([f'''''' for idx, row in stats_ods.iterrows()])}
ODS Count Media Std Min Max
ODS {idx} {int(row['count'])} {row['mean']:.4f} {row['std']:.4f} {row['min']:.4f} {row['max']:.4f}

4. ODS Más Representados en Top 50

{''.join([f'''''' for idx, count in top_50_ods.head(10).items()])}
ODS Cantidad Porcentaje
ODS {idx} {count} {count/50*100:.1f}%
""" 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"""
Gobierno de Colombia

Fondo Multidonante de las Naciones Unidas
""") # Encabezado principal with gr.Row(): with gr.Column(scale=1): gr.HTML(f"""
Logo conectaODS
""") 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"""

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

""") # 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("""

📊 Diagrama de Pareto (Regla 80/20)

  1. CRITERIO: 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.
  2. Marcos globales: (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS prioritarios por ciclo de implementación.
  3. Interactúa: Las visualizaciones son dinámicas y permiten exploración
""") 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="

No hay datos ODS para mostrar

") # 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="

No hay datos METAS para mostrar

") # # 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="

No hay datos INDICADORES para mostrar

") # # 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"""

📂 Fuente de Información: {name.replace('.xlsx','')}

Análisis masivo de iniciativas territoriales

""") 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"""

Métricas Generales

Iniciativas Analizadas
{n_iniciativas}
""" 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("""

📊 Diagrama de Pareto (Regla 80/20)

  1. CRITERIO: 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.
  2. Marcos globales: (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS prioritarios por ciclo de implementación.
  3. Interactúa: Las visualizaciones son dinámicas y permiten exploración
""") 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("""

📊 Diagrama de Pareto (Regla 80/20)

  1. CRITERIO: 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.
  2. Marcos globales: (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS prioritarios por ciclo de implementación.
  3. Interactúa: Las visualizaciones son dinámicas y permiten exploración
""") 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("""

📊 Diagrama de Pareto (Regla 80/20)

  1. CRITERIO: 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.
  2. Marcos globales: (UNDP, VNR, EU SDG Strategy) recomiendan 3-5 ODS prioritarios por ciclo de implementación.
  3. Interactúa: Las visualizaciones son dinámicas y permiten exploración
""") 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 )