Forum Discussion
Problema de renderizado visual en Python en el wrapper .Net
Tengo un panel de control de Power BI alojado en un portal de envolvimientos construido en .NET o alguna tecnología. Así que ese panel solo puede ser accedido por usuarios autorizados.
El panel trata sobre algunas encuestas. Tengo comentarios cualitativos (texto libre) de los encuestados como parte de los datos. Según las selecciones de usuario en varios filtros de slicer como género, evento, país, bu, departamento, etc., se resumen el subconjunto de comentarios (texto libre) y se muestra una breve salida de texto resumen. Esta resumen y visualización del texto se realiza usando un script en Python. Todo esto funciona como se esperaba. Sin embargo, cuando el panel de control está alojado en el portal de envoltorios, presenta errores en este aspecto visual.
También encontré una referencia que dice que los gráficos en Python o R no se renderizan en informes embebidos:
Sugiere precalcular los resultados y cargar los datos para visualizar. En mi situación, sería difícil precalcular el resumen de los comentarios porque el usuario seleccionaría cualquier permutación y combinación de filtros/slicers.
Cualquier otra solución alternativa sería muy apreciada.
Saludos
Prasad
15 Replies
- Syndicate_AdminAdministrator
Sí , @prasaddn
Los visuales de Python (y R) no son compatibles en Power BI Embedded cuando se usa un envoltorio .NET con "Embed for your customers (app owners data)", por lo que el visual funciona en Escritorio/Servicio pero falla en el portal embebido. Esto es una limitación de la plataforma, no un problema de programación. Para resolverlo, mueve la lógica de Python fuera de Power BI (por ejemplo, ejecuta la resumen de texto en Azure Functions, Databricks o Fabric y almacena los resultados) y solo muestra la salida usando visuales estándar de Power BI, o usa un visual personalizado que llame a una API de backend para resúmenes dinámicos.
- Syndicate_AdminAdministrator
Gracias por la respuesta @rohit1991 . Sí, la única alternativa que he oído es pre-computación. Sin embargo, no es factible con mi situación actual. Me gustaría saber si conoces algún vídeo personalizado gratuito que llame a una API backend para resúmenes dinámicos y que también funcione en un entorno de wrapper .NET.
- Syndicate_AdminAdministrator
Sí , @prasaddn
No existe un vídeo visual personalizado gratuito y listo para usar en Power BI que pueda llamar de forma segura a una API externa y renderizar resúmenes dinámicos de texto en un escenario Embed for your Customer (.NET / app-owns-data). Debido al sandboxing y las restricciones de seguridad, los visuales personalizados no pueden hacer libremente llamadas arbitrarias a la API a menos que estén diseñados y certificados específicamente, y la mayoría de los gráficos de AppSource no soportan este patrón. En la práctica, los enfoques soportados son construir tu propio visual personalizado que llame a un endpoint controlado de la API, o mover la lógica de sumarización fuera de Power BI (Azure Function/Fabric/Databricks) y cargar los resultados en el modelo para su visualización usando visuales estándar.
- Syndicate_AdminAdministrator
Hola @prasaddn
Como pre-calcular cada permutación de filtros no es factible para comentarios en texto libre, necesitas mover la lógica de Python "hacia arriba" en la pila
Solución 1: Mover la lógica al wrapper .NET (Pro-Code)
Como ya usas un portal .NET, puedes encargarte de la resumen fuera de Power BI.
El flujo de trabajo:
Utiliza el Power BI Client SDK en tu aplicación .NET para capturar el estado de los slicers.
Utiliza una consulta DAX a través de la API REST (Datasets - Ejecutar consultas) para obtener solo el subconjunto filtrado de comentarios.
Pasa esos comentarios a una función de Azure en Python (donde puedas reutilizar tu script existente).
Muestra el resultado en un <div> HTML estándar o en un cuadro de texto en tu portal, situado junto al informe.
Solución 2: Power Automate + AI Builder (Low-Code)
Si quieres mantener el resultado dentro del área del informe, puedes reemplazar el visual de Python por uno visual de Power Automate.
El flujo de trabajo:
Añade el visual de Power Automate para Power BI a tu informe.
Pasa el campo "Comentarios" al cubo de datos visuales.
Crea un flujo que se active al hacer clic en un botón, envíe los datos filtrados a Azure OpenAI o AI Builder, y devuelva el resumen a una tabla de "Resultados".
Beneficio: Esto evita las limitaciones del sandbox porque el "Visual" es solo un botón, y el procesamiento real ocurre en la nube.
Solución 3: El visual de "contenido HTML" (implementación más rápida)
Puedes usar el vídeo personalizado de contenido HTML (disponible en AppSource) para mostrar texto dinámico que los visuales estándar no pueden manejar.
En lugar de Python, puedes usar un Grupo de Cálculo o una medida DAX compleja para concatenar las cadenas (si la lógica de sumarización es sencilla).
Si la lógica debe ser Python, puedes usar Power Query para llamar a una API web durante la actualización, aunque esto no será "en tiempo real" para cambios en el slicer.
Si este cambio arquitectónico te ayuda a saltarte la limitación visual de Python en tu portal, ¡márcalo como una "Solución Aceptada" para ayudar a otros!
Saludos
Vishwanath
- Syndicate_AdminAdministrator
Estimado @AshokKunwar, En primer lugar, gracias por ofrecer 3 enfoques diferentes. También debo señalar que soy nuevo en Power BI.
Solución 1: Mover la lógica al wrapper .NET (Pro-Code)
No tengo Power BI Client SDK y el portal .NET también lo desarrolla otro equipo, y los usamos para alojar nuestro panel de control. así que literalmente, no tengo acceso para editar ni modificar nada en el portal. Tampoco estoy familiarizado con el uso de funciones de Azure.
Solución 2: Power Automate + AI Builder (Low-Code)
Como mencioné, no tienes exposición ni acceso a Azure, funciones de IA o modelos basados en tokens.
Solución 3: El visual de "contenido HTML" (implementación más rápida). Pensaba que podríamos tener algún vídeo personalizado que pudiera tomar la entrada como un conjunto de comentarios de texto y luego devolver un texto resumen. Sin embargo, no estoy seguro de que esto se pueda hacer con grupos de calcaluación, ya que implica entender todos los comentarios y crear un resumen breve.
Por favor, aconsejadme si el script de python adjunto aquí puede convertirse en una función DAX o en algún script que funcione con contenido HTML personalizado visual y en entorno .NET.
Perdón, porque esto va a ser un post largo:
====================importar matplotlib.pyplot como plt
Importación RE
de colecciones importación Contador
importar textwrap
# ============================================================
# CONFIG
# ============================================================
EVENT_COL = "Nombre del evento"
TEXT_COL = "¿Alguna sugerencia para mejorar tu experiencia?"
MAX_THEMES = 5 # mostrar como máximo esta cantidad de temas
MIN_COUNT_RAW = 4 # min de frecuencia para palabras sin mapear (muy estrictas)
RAW_THEMES_ON = Verdadero # establece Falso si SOLO quieres temas seleccionados de label_map
# ============================================================
# ============================================================
# PASO 1 — Extraer los tokens de nombre de eventos para que los nombres de eventos nunca se conviertan en temas
# ============================================================
Def tokenize_event_name(nombre: STR):
si no es isinstance(nombre, fuerza):
volver []
Nombre = en el nombre. Lower()
Nombre = re.sub(r"[^a-z0-9s]", " ", nombre)
return [t por t en name.split() si len(t) > 2]
event_tokens = conjunto()
Si EVENT_COL en dataset.columns:
para EV en dataset[EVENT_COL].dropna().unique():
event_tokens.update(tokenize_event_name(ev))
# ============================================================
# PASO 2 — Palabra clave robusta + diccionario de basura
# ============================================================
# Palabras clave en inglés base (estilo NLTK)
base_stopwords = {
"Yo", "yo", "mi", "mí", "nosotros", "nuestro", "nuestro", "nosotros", "tú", "tu", "tu", "tú mismo", "tú mismo",
"él", "él", "su", "él mismo", "ella", "ella", "ella", "ella", "ella", "es", "él", "él",
"ellos", "ellos", "su", "suyos", "ellos mismos",
"qué", "quién", "quién", "quién", "esto", "aquello", "esto", "aquello",
"soy", "es", "eres", "fue", "sido",
"tiene", "tiene", "tenía", "teniendo", "hay", "hace",
"a", "an", "el", "y", "pero", "si", "o", "porque", "como", "hasta que", "mentres",
"de", "en", "por", "a favor", "con", "sobre", "contra", "entre", "en", "a través", "durante",
"antes", "después", "arriba", "abajo", "hasta", "de", "arriba", "abajo", "dentro", "fuera", "enciende", "apagado", "encima", "abajo",
"otra vez", "más", "luego", "una vez", "aquí", "allá", "cuando", "dónde", "por qué", "cómo",
"todos", "cualquiera", "ambos", "todos", "pocos", "más", "mayores", "otros", "algunos", "tales",
"no", "no", "no", "solo", "posee", "igual", "así", "que", "demasiado", "muy",
"puede", "hará", "solo", "no debería", "ahora", "también", "por favor", "incluir", "mejor"
}
# Palabras extra basura/estructurales específicas de sugerencias contexto
extra_junk = {
# Elogios genéricos/rellenos – no temas
"Bien", "Genial", "Bien", "Increíble", "Increíble", "Excelente", "Fantástico", "Soberbio", "Maravilloso", "Mejor",
"Bueno", "bien", "vale", "vale", "super", "memorable", "disfrutable", "satisfactorio",
# Cortesía / gracias
"gracias", "gracias", "gracias", "gracias!", "gracias", "¡gracias!", "gracias!!", "gracias🙏","gracias🙏",
# Eco de preguntas / palabras de relleno
"sugerencias", "sugerencias", "mejorar", "mejorar", "mejoras", "experiencia",
"retroalimentación", "entradas", "entrada", "puntos", "puntos", "comentarios", "compartir", "compartir", "compartir",
# Sustantivos genéricos de eventos
"evento", "eventos", "sesión", "sesiones", "reuniones", "reuniones", "programa", "programas",
"show", "shows", "función", "funciones", "día", "días", "hoy", "ayer", "mañana",
"tarde", "mañana", "tarde",
# Verbos genéricos y cosas vagas
"tenía", "tenía", "hizo", "hizo", "haciendo", "hicido", "hago", "hace", "toma", "toma", "tomada",
"ver", "ver", "ver", "querer", "querer", "querría", "podría", "debería", "esperanza", "esperaba", "esperaba",
"mirando", "mirando", "sintiendo", "sintiendo", "disfrutando", "disfrutando", "gustando", "me gusta", "amor", "amado",
"apreciar", "apreciar", "ayudar", "ayudar", "ayudar", "gestionar", "arreglar", "organizar", "organizar",
"planificación", "planificada", "coordinada", "coordinada", "apoyo", "apoyo",
# Breves rellenos conversacionales
"sí", "sí", "sí", "vale", "vale", "hmm", "hmmm", "jajaja", ja, jajaja, jajaja.
# Emojis / fichas basura
"👍","👏","😊","🙏","😀","😄","😂","😁","🙂","😉","😇","🎉","🎊","💯","❤️","♥","😁😁","😂😂",
# Palabras conocidas de basura/estructurales que no quieres como temas
"camp", # a menudo forma parte del nombre del evento como "Campo de Donación de Sangre"
}
# Combina palabras de paso base, basura personalizada y fichas de evento
palabras clave = base_stopwords | extra_junk | event_tokens
# ============================================================
# PASO 3 — Tokenizador con filtrado fuerte
# ============================================================
Def clean_tokens(texto: STR):
si no es isinstance (texto, str):
volver []
texto = texto.lower()
texto = re.sub(r"[^a-z0-9s]", " ", texto)
fichas = []
para t en text.split():
si no es t:
continúa
t = t.strip()
# Saltarse números puros
si t.isdigit():
continúa
# saltarse fichas muy cortas
si len(t) <= 2:
continúa
# Salta palabras de parada y piezas de nombres de eventos
Si T en palabras clave o T en event_tokens:
continúa
tokens.append(t)
Tokens de retorno
# ============================================================
# PASO 4 — Diccionario de temas (label_map) ajustado para mejoras
# ============================================================
label_map = {
# Multitud / cola / flujo
"multitud": "multitud y colas",
"abarrotado": "multitud y colas",
"cola": "multitud y colas",
"colas": "multitud y colas",
"línea": "multitud y colas",
"líneas": "multitud y colas",
"Entrada": "Multitud y colas",
"salida": "multitud y colas",
"flow": "multitud y colas",
# Tiempo / horario / duración
"Tiempo": "Gestión del tiempo",
"retraso": "gestión del tiempo",
"retrasos": "gestión del tiempo",
"tarde": "gestión del tiempo",
"Antes": "Gestión del tiempo",
"duración": "gestión del tiempo",
"horario": "gestión del tiempo",
"reprogramar": "gestión del tiempo",
# Lugar / asientos / instalaciones
"Lugar": "Espacio e Instalaciones",
"Salón": "Lugar e instalaciones",
"auditorio": "Espacio e instalaciones",
"ubicación": "sede e instalaciones",
"espacio": "asientos y espacio",
"Asientos": "Asientos y espacio",
"Asiento": "Asientos y espacio",
"Asientos": "Asientos y espacio",
"sillas": "asientos y espacio",
"Silla": "Asientos y espacio",
"fan": "sede e instalaciones",
"fans": "sede e instalaciones",
"AC": "Lugar e Instalaciones",
"aire": "Lugar e instalaciones",
"sonido": "sonido y técnico",
"Audio": "Sonido y Técnica",
"mic": "sonido y técnica",
"Altavoz": "Sonido y Técnica",
"altavoces": "sonido y técnica",
"Luz": "Iluminación y Ambiente",
"luces": "iluminación y ambiente",
"iluminación": "iluminación y ambiente",
# Comida / tentempiés / agua
"Tentempié": "Aperitivos y refrescos",
"Aperitivos": "Aperitivos y refrescos",
"refrescos": "aperitivos y refrescos",
"refrescos": "aperitivos y refrescos",
"comida": "aperitivos y refrescos",
"Almuerzo": "Aperitivos y refrescos",
"Cena": "Aperitivos y refrescos",
"agua": "aperitivos y refrescos",
"Botella": "Aperitivos y refrescos",
"botellas": "aperitivos y refrescos",
# Registro / proceso / comunicación
"registro": "registro y proceso",
"registro": "registro y proceso",
"formularios": "registro y proceso",
"formulario": "registro y proceso",
"proceso": "registro y proceso",
"procedimiento": "registro y proceso",
"instrucciones": "comunicación e información",
"Información": "Comunicación e información",
"Info": "Comunicación e información",
"comunicación": "comunicación e información",
"actualización": "comunicación e información",
"Actualizaciones": "Comunicación e información",
"correo": "comunicación e información",
"correo electrónico": "comunicación e información",
"WhatsApp": "Comunicación e información",
"notificación": "comunicación e información",
"notificaciones": "comunicación e información",
# Aparcamiento / transporte / acceso
"aparcamiento": "aparcamiento y acceso",
"parque": "aparcamiento y acceso",
"punto de entrada": "aparcamiento y acceso",
"puerta": "aparcamiento y acceso",
"tráfico": "aparcamiento y acceso",
# Niños y familia
"niños": "niños y actividades familiares",
"niños": "actividades para niños y familia",
"Familia": "Niños y actividades familiares",
"Familias": "Niños y actividades familiares",
"Puesto": "Actividades para niños y familia",
"foto": "actividades para niños y familia",
"fotos": "actividades para niños y familia",
"photobooth": "actividades para niños y familia",
"juegos": "actividades para niños y familia", # puestos de juegos para niños, etc.
"juego": "actividades para niños y familia",
# Frecuencia / eventos futuros
"frecuencia": "frecuencia y cadencia de eventos",
"regular": "frecuencia y cadencia de eventos",
"regularmente": "frecuencia y cadencia de eventos",
"a menudo": "frecuencia y cadencia de eventos",
"mensual": "frecuencia y cadencia de eventos",
"anual": "frecuencia y cadencia de eventos",
"trimestral": "frecuencia y cadencia de eventos",
# Contenido / actividades / participación
"contenido": "contenido y actividades",
"actividades": "contenido y actividades",
"actividad": "contenido y actividades",
"sesión": "contenido y actividades",
"Sesiones": "Contenido y actividades",
"variedad": "contenido y actividades",
"opciones": "contenido y actividades",
"opciones": "contenido y actividades",
# Seguridad / higiene
"seguridad": "seguridad e higiene",
"Seguro": "Seguridad e Higiene",
"higiene": "seguridad e higiene",
"Limpieza": "Seguridad e Higiene",
"limpieza": "seguridad e higiene",
"baño": "seguridad e higiene",
"baños": "seguridad e higiene",
"Inodoro": "Seguridad e Higiene",
"baños": "seguridad e higiene",
# Personal / voluntarios
"Voluntarios": "Personal y voluntarios",
"voluntarios": "personal y voluntarios",
"personal": "personal y voluntarios",
"Helpdesk": "Personal y voluntarios",
}
# ============================================================
# PASO 5 — Resumidor
# ============================================================
Def summarize_group(mensajes):
fichas = []
Para la testosterona en los textos:
tokens.extend(clean_tokens(t))
Si no son fichas:
volver ""
conteos = Contador (tokens)
# 1) Agrega temas mapeados
theme_counts = Contador()
para palabra, c en cuenta.elementos():
si la palabra está en label_map:
theme_counts[label_map[palabra]] += c
# 2) Opcional: Añadir palabras muy fuertes y no mapeadas como temas en bruto
Si RAW_THEMES_ON:
para palabra, c en cuenta.elementos():
si la palabra está en label_map:
continúa
Si c < MIN_COUNT_RAW:
continúa
# Evita verbos/adverbios obvios
si word.terminswith(("ed", "ing", "ly", "s")):
continúa
si len(palabra) <= 3:
continúa
si la palabra está en event_tokens:
continúa
theme_counts[palabra] += c
Si no theme_counts:
volver ""
top_themes = [tema para el tema, _ en theme_counts.most_common(MAX_THEMES)]
Si no top_themes:
volver ""
if len(top_themes) == 1:
return f"Los participantes sugirieron mejoras con mayor frecuencia en {top_themes[0]}."
Elif Len(top_themes) == 2:
regreso (
f"Los participantes sugirieron mejoras con mayor frecuencia en "
f"{top_themes[0]} y {top_themes[1]}."
)
De lo contrario:
regreso (
"Los participantes sugirieron mejoras con mayor frecuencia en "
+ ", ".join(top_themes[:-1])
+ f", y {top_themes[-1]}."
)
Def wrap_text(texto, ancho=80):
return "n".join(textwrap.wrap(text, width))
# ============================================================
# APLICAR A DATOS FILTRADOS ACTUALES
# ============================================================
si no TEXT_COL en dataset.columns:
summary_text = f"Columna '{TEXT_COL}' no encontrada en la imagen actual."
De lo contrario:
textos = dataset[TEXT_COL].dropna()
summary_text = summarize_group(textos)
Si no summary_text:
summary_text = "No se han encontrado temas de mejora recurrentes claros para la selección actual."
envuelta = wrap_text(summary_text)
# ============================================================
# DIBUJA VISUAL — resistente contra el recorte
# ============================================================
fig_w, fig_h, dpi = 20, 4, 120
tamaño de fuente = 40
# Opcional: envolvimiento más inteligente basado en la figura + tamaño de fuente
def dynamic_wrap(texto, fig_width_in=fig_w, dpi=dpi, font_size=tamaño de fuente, char_width_px=0,6):
px_width = fig_width_in * dpi
approx_char_px = font_size * char_width_px
width_chars = max(30, int(px_width / approx_char_px))
return "n".join(textwrap.wrap(text, width=width_chars))
envuelta = dynamic_wrap(summary_text)
fig = plt.figure(figsize=(fig_w, fig_h), dpi=dpi)
# Colocar texto en coordenadas FIGURE para evitar el recorte de ejes
fig.text(0.5, 0.5, envuelto,
ha="center", va="center",
tamaño de la fuente=tamaño de la fuente, peso de la fuente='negrita',
color='azul oscuro',
wrap=True,
bbox=dict(boxstyle='redondo,pad=0.5', colorcolor/cara='blanco', alfa=0.95, colorcolor/aristas='blanco'))
plt.tight_layout(pad=2.0)
plt.show()====================
- Syndicate_AdminAdministrator
Hola @prasaddn
Por favor, revisa la muestra adjunta para ver si ofrece alguna información para hacerlo con DAX.
- Syndicate_AdminAdministrator
Hola @prasaddn
¿Podrías explicar qué limitaciones tenías con los métodos tradicionales de Power BI que te impedían hacer lo que planeas hacer con Python? Si pudieras explicar esas limitaciones, podría ayudarte encontrar una solución alternativa.
- Syndicate_AdminAdministrator
Hola @prasaddn
Disculpa por leer mal tu mensaje original y por haber revisado otras respuestas que habías dado. Creo que ahora entiendo tu situación, así que he borrado mi comentario anterior.
Si lo entiendo bien, básicamente estás buscando una solución alternativa para python que use una API como fuente de datos que funcione como DirectQuery. Si es así, por favor echa un vistazo a este artículo:
- Syndicate_AdminAdministrator
Hola @kushanNa En mi otra respuesta he publicado mi script en Python que funciona como se esperaba. Sin embargo, no es compatible con el panel de control alojado o incrustado en un wrapper .NET. Por eso, busco formas alternativas de resumir los comentarios de la encuesta, como algún vídeo o script personalizado que soporte wrapper en .NET.
- Syndicate_AdminAdministrator
Hola @prasaddn
Para ser sincero, como no soy desarrollador de aplicaciones ni web, no estoy muy familiarizado con los wrappers de .NET.
Sin embargo, puedo sugerirte una solución para que pruebes. Power BI Embedded Analytics puede detectar selecciones de slicer.
Hice una prueba y he escrito el siguiente blog para demostrar cómo puedes capturar selecciones de slicers usando Power BI Embedded Analytics con HTML y Javascript;
https://community.fabric.microsoft.com/t5/Power-BI-Community-Blog/Real-Time-Cross-Report-Filtering-Using-JavaScript-embedded/ba-p/4850521Una vez que captures las selecciones de slicer> puedes enviarlas a tu lógica de resumen usando una API->guardarlas en una fuente de datos donde DirectQuery esté habilitado, y luego extraer esos datos a tu visual.
- Syndicate_AdminAdministrator
Los visuales de Python (y R) no están compatibles en escenarios embebidos. Se renderizan en Escritorio (y a veces en la interfaz de Servicio), pero cuando alojas el informe dentro de un "envoltorio" .NET (Power BI Embedded / iframe / app posee datos), el Servicio no ejecutará ese script en Python por razones de seguridad o de ejecución — así que la imagen falla. Eso es un comportamiento esperado, no un error que puedas arreglar con los ajustes.
Qué hacer en su lugar:
1. Precomputo + corte
Si puedes reducir los grados de libertad, precalcula resúmenes a un grano razonable (por ejemplo, País × BU × Departamento × Evento) y deja que el informe filtre esos resultados. No necesitas "todas las permutaciones", solo las combinaciones que realmente utiliza tu negocio.2. Resumen de movimientos fuera de Power BI
Mantén el informe interactivo, pero haz el resumen en un servicio externo:Envía el contexto actual del filtro (valores seleccionados) a una API
La API devuelve el texto resumen
Muéstralo en Power BI usando un visual personalizado (HTML/React) o un visual de Power BI que pueda llamar a un endpoint
Esta es la única forma de mantener la dinámica de "cualquier combinación de slicers" en embebida.
3. Utilizar una canalización de Análisis de Texto aguas arriba
Si estás en Fabric / Azure, puedes almacenar incrustaciones/temas o "frases clave" por comentario y resumir según la recuperación filtrada (mucho más ligero que reresumir texto en bruto cada vez). El informe luego agrega señales precomputadas.4. Si debes quedarte únicamente en Power BI Embedded
Entonces no puedes usar visuales en Python/R. Tendrás que reemplazar el visual de Python por:un visual estándar basado en tablas precomputadas, o
un visual personalizado certificado que no requiera ejecución en Python en el cliente.