auto(0129): agents_dashboard — secret_store_cpp_infra + CMakeLists register #4
@@ -13,6 +13,7 @@ from .setup import metabase_setup
|
||||
from .maintenance import metabase_fix_null_ratio, metabase_pair_n_n1_columns
|
||||
from .metabase_mbql_validate import metabase_mbql_validate
|
||||
from .metabase_update_dashboard_safe import metabase_update_dashboard_safe
|
||||
from .smartscalar import metabase_smartscalar_kpi_sql, metabase_smartscalar_dimension_tag, metabase_smartscalar_kpi_payload
|
||||
|
||||
__all__ = [
|
||||
"MetabaseClient",
|
||||
@@ -38,4 +39,5 @@ __all__ = [
|
||||
"metabase_pair_n_n1_columns",
|
||||
"metabase_mbql_validate",
|
||||
"metabase_update_dashboard_safe",
|
||||
"metabase_smartscalar_kpi_sql", "metabase_smartscalar_dimension_tag", "metabase_smartscalar_kpi_payload",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: metabase_smartscalar_dimension_tag
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def metabase_smartscalar_dimension_tag(*, name: str, field_id: int, base_type: str = 'type/Text', widget_type: str = 'string/=', display_name: str = '') -> dict"
|
||||
description: "Construye un template-tag de tipo dimension con la firma exacta que Metabase v0.59 espera (incluyendo lib/uuid, base-type, effective-type). Sin estos flags Metabase devuelve PUT 200 pero descarta silenciosamente el dataset_query."
|
||||
tags: [metabase, smartscalar, template-tag, field-filter, kpi, pure, builder]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports:
|
||||
- "import uuid"
|
||||
params:
|
||||
- name: name
|
||||
desc: "Identificador snake_case del template-tag; tambien se usa en el SQL como [[AND {{name}}]]"
|
||||
- name: field_id
|
||||
desc: "ID numerico del Field de Metabase al que apunta el filtro; consultable via GET /api/field/<id> o el data model"
|
||||
- name: base_type
|
||||
desc: "Tipo base del Field. Valores comunes: type/Text, type/Date, type/Integer, type/Decimal, type/DateTime"
|
||||
- name: widget_type
|
||||
desc: "Widget de filtro en el dashboard. Comunes: string/= (multivalor texto), date/all-options, number/=, string/contains"
|
||||
- name: display_name
|
||||
desc: "Etiqueta legible mostrada en la UI; vacio = autogenerada title-casing del name"
|
||||
output: "dict con la estructura {name, id, display-name, type, widget-type, dimension: ['field', {base-type, effective-type, lib/uuid}, field_id]} listo para usar como entrada del map template-tags de un dataset_query nativo"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/smartscalar.py"
|
||||
---
|
||||
|
||||
## Por que existe
|
||||
|
||||
Metabase v0.59 valida silenciosamente la estructura de los ``template-tags``
|
||||
en cards nativas: si falta ``lib/uuid``, ``base-type`` o ``effective-type``
|
||||
en el ``dimension`` array, el ``PUT /api/card/:id`` devuelve 200 pero al
|
||||
releer la card el ``dataset_query`` aparece vacio (``stages: [{}]``) y la
|
||||
card no se puede ejecutar.
|
||||
|
||||
Este helper rellena la firma exacta y ademas genera el ``lib/uuid``
|
||||
deterministicamente con ``uuid5`` sobre el ``name``, garantizando que la
|
||||
funcion sea pura: dos llamadas con los mismos argumentos producen el mismo
|
||||
dict bit a bit.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
tags = {
|
||||
"fecha": metabase_smartscalar_dimension_tag(
|
||||
name="fecha", field_id=322392,
|
||||
base_type="type/Date", widget_type="date/all-options",
|
||||
display_name="Fecha",
|
||||
),
|
||||
"categoria": metabase_smartscalar_dimension_tag(
|
||||
name="categoria", field_id=270702,
|
||||
widget_type="string/=",
|
||||
),
|
||||
}
|
||||
# tags["fecha"]["dimension"] == ["field", {"base-type": "type/Date", ...}, 322392]
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- El ``id`` interno se rellena con ``f"{name}_tag"`` por consistencia con las
|
||||
cards de la suite Lean.
|
||||
- Para campos calculados (expresiones MBQL) o template-tags de tipo ``text``,
|
||||
``snippet`` o ``card``, NO usar este helper — fueron disenados solo para
|
||||
dimension/field-filters.
|
||||
- Si la card se va a guardar una vez y editar a mano luego, los UUIDs
|
||||
deterministicos no causan colisiones porque solo importan dentro de la card.
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: metabase_smartscalar_kpi_payload
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def metabase_smartscalar_kpi_payload(*, name: str, database_id: int, sql: str, template_tags: dict | None = None, description: str = '', collection_id: int = 0, currency: bool = False, currency_code: str = 'EUR', decimals: int = 0, comparison_label: str = 'vs n-1', extra_visualization_settings: dict | None = None) -> dict"
|
||||
description: "Construye el payload completo para POST /api/card de un KPI smartscalar con formato y comparacion previousValue. Combina SQL nativo (idealmente generado por metabase_smartscalar_kpi_sql) con template-tags y visualization_settings predefinidos. Listo para metabase_create_card_raw."
|
||||
tags: [metabase, smartscalar, kpi, card, payload, pure, builder]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
params:
|
||||
- name: name
|
||||
desc: "Nombre de la card mostrado en el dashboard"
|
||||
- name: database_id
|
||||
desc: "ID de la database de Metabase contra la que se ejecuta el SQL"
|
||||
- name: sql
|
||||
desc: "Query nativa que devuelve columnas periodo (DATE) y valor (numero) con 2 filas (n-1, actual). Generable con metabase_smartscalar_kpi_sql"
|
||||
- name: template_tags
|
||||
desc: "Dict {tag_name: tag_dict} para field-filters; None para card sin filtros. Construir con metabase_smartscalar_dimension_tag"
|
||||
- name: description
|
||||
desc: "Descripcion mostrada en la card; vacio = autorrellenada"
|
||||
- name: collection_id
|
||||
desc: "ID de la coleccion destino; 0 = Our analytics (root)"
|
||||
- name: currency
|
||||
desc: "Si True formatea valor como moneda con simbolo currency_code"
|
||||
- name: currency_code
|
||||
desc: "Codigo ISO de moneda. Default EUR. Solo aplica si currency=True"
|
||||
- name: decimals
|
||||
desc: "Decimales mostrados en valor"
|
||||
- name: comparison_label
|
||||
desc: "Etiqueta del bloque de comparacion bajo el numero principal. Default 'vs n-1'"
|
||||
- name: extra_visualization_settings
|
||||
desc: "Settings adicionales fusionados (top-level override) en visualization_settings; util para anadir scalar.title, card.title.alignment, etc."
|
||||
output: "dict con payload completo (name, description, type=question, display=smartscalar, dataset_query nativo, visualization_settings con scalar.field/scalar.comparisons/column_settings, [collection_id]) directamente posteable a /api/card"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/smartscalar.py"
|
||||
---
|
||||
|
||||
## Por que existe
|
||||
|
||||
Encapsular la combinacion exacta de campos que Metabase v0.59 necesita para
|
||||
un KPI ``smartscalar`` con comparacion ``previousValue``: ``scalar.field``,
|
||||
``scalar.comparisons`` con ``id``, ``type`` y ``label``, y ``column_settings``
|
||||
con la clave de columna serializada como JSON ``'["name","valor"]'``.
|
||||
|
||||
Cualquier desviacion (clave de column_settings sin escapar, ausencia de
|
||||
``scalar.field``, ``type`` distinto de ``previousValue/anotherColumn/staticNumber``)
|
||||
hace que el frontend muestre el numero pero descarta silenciosamente la
|
||||
comparacion o pide breakout.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
sql = metabase_smartscalar_kpi_sql(
|
||||
act_expr="ROUND(SUM(v.venta_n), 2)",
|
||||
n1_expr="ROUND(SUM(v.venta_n1), 2)",
|
||||
body_sql=ventas_cte_sql,
|
||||
date_expr="MIN(v.fecha)",
|
||||
)
|
||||
tags = {
|
||||
"fecha": metabase_smartscalar_dimension_tag(
|
||||
name="fecha", field_id=322392,
|
||||
base_type="type/Date", widget_type="date/all-options",
|
||||
),
|
||||
}
|
||||
payload = metabase_smartscalar_kpi_payload(
|
||||
name="Venta total",
|
||||
database_id=6,
|
||||
sql=sql,
|
||||
template_tags=tags,
|
||||
currency=True, decimals=0,
|
||||
description="Venta del periodo seleccionado vs ano anterior.",
|
||||
collection_id=583,
|
||||
)
|
||||
card = metabase_create_card_raw(client, payload)
|
||||
print(card["id"])
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- ``visualization_settings.column_settings`` usa la clave con formato JSON
|
||||
exacto ``'["name","valor"]'`` (sin espacios entre comillas y coma) — Metabase
|
||||
no normaliza variaciones.
|
||||
- Si necesitas la card como ``model`` o ``metric`` en lugar de ``question``,
|
||||
override via ``extra_visualization_settings`` no aplica — clona el resultado
|
||||
y modifica ``payload["type"]`` antes de enviar.
|
||||
- Para reemplazar una card existente usa ``metabase_update_card`` con los
|
||||
mismos campos del payload (display, dataset_query, visualization_settings).
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: metabase_smartscalar_kpi_sql
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def metabase_smartscalar_kpi_sql(*, act_expr: str, n1_expr: str, body_sql: str, date_expr: str = 'MIN(fecha)') -> str"
|
||||
description: "Envuelve agregaciones actual+n-1 en el patron de 2 filas (periodo, valor) que el display smartscalar de Metabase v0.59 requiere para mostrar comparacion vs ano anterior sin pedir breakout temporal. Genera SQL nativo BigQuery con UNION ALL d_min/d_min-52w."
|
||||
tags: [metabase, smartscalar, kpi, sql, bigquery, pure, builder]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
params:
|
||||
- name: act_expr
|
||||
desc: "Expresion SQL de agregado para el periodo actual, ej. 'ROUND(SUM(v.venta_n), 2)' o 'SAFE_DIVIDE(SUM(a), NULLIF(SUM(b),0))'"
|
||||
- name: n1_expr
|
||||
desc: "Expresion SQL para el mismo agregado del ano anterior, ej. 'ROUND(SUM(v.venta_n1), 2)'"
|
||||
- name: body_sql
|
||||
desc: "Cuerpo SQL desde 'FROM' que define las tablas/CTEs y filtros con template-tags Metabase ([[AND {{tag}}]])"
|
||||
- name: date_expr
|
||||
desc: "Expresion para extraer la fecha minima del periodo; usada como periodo en la fila actual y ancla para DATE_SUB de la fila n-1. Default 'MIN(fecha)'"
|
||||
output: "string con SQL nativo BigQuery que devuelve 2 filas con columnas periodo (DATE) y valor (numero); fila 1 = periodo n-1, fila 2 = periodo actual"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/metabase/smartscalar.py"
|
||||
---
|
||||
|
||||
## Por que existe
|
||||
|
||||
El display ``smartscalar`` de Metabase v0.59.4 con ``previousValue`` requiere
|
||||
una serie temporal para mostrar comparacion. Si el query devuelve un solo
|
||||
numero el frontend muestra el error "Agrupa solo por un campo de tiempo para
|
||||
ver como ha cambiado con el tiempo".
|
||||
|
||||
El truco probado en el dashboard ``Informe Lean`` (cards 9340-9373) es
|
||||
producir 2 filas sinteticas: una para el periodo actual (``d_min``, ``act``)
|
||||
y otra para n-1 (``d_min - 52 weeks``, ``n1``). Smartscalar lo interpreta como
|
||||
serie de 2 puntos y compara naturalmente.
|
||||
|
||||
Esta funcion automatiza ese patron: el caller solo provee el cuerpo SQL con
|
||||
sus filtros y las dos expresiones de agregado.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
body = """
|
||||
FROM `proj.ds.base_margenes_aa` `proj.ds.base_margenes_aa` v
|
||||
LEFT JOIN `proj.ds.Objeto_productos` p ON p.nav_id = v.prod_nav_id
|
||||
WHERE 1=1
|
||||
[[AND {{fecha}}]]
|
||||
[[AND {{categoria}}]]
|
||||
[[AND {{tipo}}]]
|
||||
"""
|
||||
|
||||
sql = metabase_smartscalar_kpi_sql(
|
||||
act_expr="ROUND(SUM(v.venta_n), 2)",
|
||||
n1_expr="ROUND(SUM(v.venta_n1), 2)",
|
||||
body_sql=body,
|
||||
date_expr="MIN(v.fecha)",
|
||||
)
|
||||
# sql tiene UNION ALL con periodo y valor
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- Por convencion los template-tags estan envueltos en ``[[ ]]`` para que sean
|
||||
opcionales: si el dashboard no aplica el filtro la clausula desaparece.
|
||||
- Para que los field-filters de Metabase resuelvan correctamente las tablas
|
||||
dentro de CTEs, alias cada tabla con la cadena ``schema.table`` exacta que
|
||||
Metabase usa al expandir el template-tag (ej. ``\`proj.ds.tabla\` \`ds.tabla\``).
|
||||
- El SQL generado usa ``DATE_SUB(d_min, INTERVAL 52 WEEK)`` (BigQuery dialect).
|
||||
Para Postgres adaptar a ``d_min - INTERVAL '52 weeks'``.
|
||||
@@ -0,0 +1,282 @@
|
||||
"""Helpers puros para construir KPIs con display=smartscalar en Metabase.
|
||||
|
||||
El display ``smartscalar`` de Metabase v0.59 muestra un numero grande con
|
||||
comparacion (% variacion + flecha verde/roja) cuando el query devuelve una serie
|
||||
temporal y ``scalar.comparisons`` esta configurado. Para mostrar "total del
|
||||
periodo seleccionado vs mismo periodo del ano anterior" sin que el motor pida
|
||||
un breakout temporal, el truco probado en el dashboard ``Informe Lean`` es
|
||||
producir 2 filas: ``(d_min - 52 semanas, n-1_total)`` y ``(d_min, total_actual)``,
|
||||
con columnas ``periodo`` y ``valor``. Smartscalar lo interpreta como serie de
|
||||
2 puntos y compara natural con ``previousValue``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
|
||||
_TAG_NAMESPACE = uuid.UUID("c0ffee00-6e75-4b14-9e8e-bea7ed5ca1ab")
|
||||
|
||||
|
||||
def metabase_smartscalar_kpi_sql(
|
||||
*,
|
||||
act_expr: str,
|
||||
n1_expr: str,
|
||||
body_sql: str,
|
||||
date_expr: str = "MIN(fecha)",
|
||||
) -> str:
|
||||
"""Envuelve una agregacion en el patron de 2 filas que smartscalar requiere.
|
||||
|
||||
Recibe las expresiones de agregado para periodo actual y n-1, mas el cuerpo
|
||||
SQL (CTEs + JOINs + WHERE con template-tags) que produce la tabla a agregar,
|
||||
y devuelve la query nativa completa lista para incrustar en una card. La
|
||||
salida tiene siempre el mismo shape: 2 filas con columnas ``periodo``
|
||||
(DATE) y ``valor`` (numero).
|
||||
|
||||
Args:
|
||||
act_expr: Expresion SQL para el total del periodo actual. Tipicamente
|
||||
``SUM(...)`` o ``SAFE_DIVIDE(SUM(a), NULLIF(SUM(b), 0))``. Debe poder
|
||||
evaluarse contra el alias del cuerpo (por ejemplo ``v.venta_n``).
|
||||
n1_expr: Equivalente para el mismo periodo del ano anterior.
|
||||
body_sql: Cuerpo SQL que define las tablas/CTEs y termina con un
|
||||
``FROM ... [JOIN ...] [WHERE ...]`` referenciable como ``ventas v``,
|
||||
o equivalente. Ejemplo en docstring de salida.
|
||||
date_expr: Expresion para extraer la fecha minima del periodo, usada
|
||||
como ``periodo`` en la fila actual y como ancla para calcular la
|
||||
fecha n-1 con ``DATE_SUB(..., INTERVAL 52 WEEK)``. Default
|
||||
``MIN(fecha)``. Para queries con alias usar ``MIN(v.fecha)``.
|
||||
|
||||
Returns:
|
||||
SQL nativo (BigQuery dialect por defecto) con la estructura:
|
||||
|
||||
WITH agg AS (
|
||||
SELECT COALESCE({date_expr}, CURRENT_DATE()) AS d_min,
|
||||
{act_expr} AS act,
|
||||
{n1_expr} AS n1
|
||||
{body_sql}
|
||||
)
|
||||
SELECT DATE_SUB(d_min, INTERVAL 52 WEEK) AS periodo, n1 AS valor FROM agg
|
||||
UNION ALL
|
||||
SELECT d_min, act FROM agg
|
||||
ORDER BY periodo
|
||||
|
||||
Example:
|
||||
>>> body = '''
|
||||
... FROM `proj.ds.base_margenes_aa` v
|
||||
... WHERE 1=1 [[AND {{fecha}}]] [[AND {{tipo}}]]'''
|
||||
>>> sql = metabase_smartscalar_kpi_sql(
|
||||
... act_expr="ROUND(SUM(v.venta_n), 2)",
|
||||
... n1_expr="ROUND(SUM(v.venta_n1), 2)",
|
||||
... body_sql=body,
|
||||
... date_expr="MIN(v.fecha)",
|
||||
... )
|
||||
>>> assert "UNION ALL" in sql
|
||||
>>> assert "periodo" in sql and "valor" in sql
|
||||
"""
|
||||
inner_select = (
|
||||
f" SELECT\n"
|
||||
f" COALESCE({date_expr}, CURRENT_DATE()) AS d_min,\n"
|
||||
f" {act_expr} AS act,\n"
|
||||
f" {n1_expr} AS n1\n"
|
||||
f" {body_sql.strip()}"
|
||||
)
|
||||
return (
|
||||
"WITH agg AS (\n"
|
||||
f"{inner_select}\n"
|
||||
")\n"
|
||||
"SELECT DATE_SUB(d_min, INTERVAL 52 WEEK) AS periodo, n1 AS valor FROM agg\n"
|
||||
"UNION ALL\n"
|
||||
"SELECT d_min, act FROM agg\n"
|
||||
"ORDER BY periodo"
|
||||
)
|
||||
|
||||
|
||||
def metabase_smartscalar_dimension_tag(
|
||||
*,
|
||||
name: str,
|
||||
field_id: int,
|
||||
base_type: str = "type/Text",
|
||||
widget_type: str = "string/=",
|
||||
display_name: str = "",
|
||||
) -> dict:
|
||||
"""Construye un template-tag de tipo ``dimension`` listo para Metabase v0.59.
|
||||
|
||||
Las cards nativas con filtros (field-filters) requieren un dict por
|
||||
template-tag con la firma exacta que espera el frontend MBQL5. Esta funcion
|
||||
rellena ``id``, ``display-name``, ``type``, ``widget-type`` y ``dimension``
|
||||
con los flags ``base-type``, ``effective-type`` y ``lib/uuid`` que sin los
|
||||
cuales Metabase silenciosamente descarta el query al guardar (PUT 200 sin
|
||||
persistir el ``dataset_query``).
|
||||
|
||||
El ``lib/uuid`` se deriva deterministicamente del nombre del tag con
|
||||
``uuid5`` para que la funcion sea pura: dos llamadas con el mismo ``name``
|
||||
producen el mismo UUID. Esto es seguro porque ``lib/uuid`` solo necesita ser
|
||||
unico dentro de la misma card.
|
||||
|
||||
Args:
|
||||
name: Identificador del template-tag (snake_case). Tambien sirve como
|
||||
nombre del filtro en el SQL: ``[[AND {{name}}]]``.
|
||||
field_id: ID numerico del Field de Metabase al que apunta el filtro.
|
||||
Se obtiene con ``GET /api/field/<id>`` o navegando el data model.
|
||||
base_type: Tipo base del Field (ej. ``type/Text``, ``type/Date``,
|
||||
``type/Integer``, ``type/Decimal``).
|
||||
widget_type: Tipo de widget en el dashboard. Comunes:
|
||||
``string/=`` (multivalor texto), ``date/all-options`` (fecha),
|
||||
``number/=``, ``string/contains``.
|
||||
display_name: Etiqueta legible mostrada en la UI cuando alguien
|
||||
ejecuta la card directamente. Vacio = se autogenera title-casing
|
||||
del ``name``.
|
||||
|
||||
Returns:
|
||||
Dict con la estructura ``{"name", "id", "display-name", "type",
|
||||
"widget-type", "dimension": ["field", {...}, field_id]}``.
|
||||
|
||||
Example:
|
||||
>>> tag = metabase_smartscalar_dimension_tag(
|
||||
... name="fecha", field_id=322392,
|
||||
... base_type="type/Date", widget_type="date/all-options",
|
||||
... display_name="Fecha",
|
||||
... )
|
||||
>>> assert tag["dimension"][2] == 322392
|
||||
>>> assert tag["widget-type"] == "date/all-options"
|
||||
>>> # UUID determinista: misma llamada produce mismo dict
|
||||
>>> assert tag == metabase_smartscalar_dimension_tag(
|
||||
... name="fecha", field_id=322392,
|
||||
... base_type="type/Date", widget_type="date/all-options",
|
||||
... display_name="Fecha",
|
||||
... )
|
||||
"""
|
||||
label = display_name or name.replace("_", " ").title()
|
||||
tag_uuid = str(uuid.uuid5(_TAG_NAMESPACE, name))
|
||||
return {
|
||||
"name": name,
|
||||
"id": f"{name}_tag",
|
||||
"display-name": label,
|
||||
"type": "dimension",
|
||||
"widget-type": widget_type,
|
||||
"dimension": [
|
||||
"field",
|
||||
{
|
||||
"base-type": base_type,
|
||||
"effective-type": base_type,
|
||||
"lib/uuid": tag_uuid,
|
||||
},
|
||||
field_id,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def metabase_smartscalar_kpi_payload(
|
||||
*,
|
||||
name: str,
|
||||
database_id: int,
|
||||
sql: str,
|
||||
template_tags: dict | None = None,
|
||||
description: str = "",
|
||||
collection_id: int = 0,
|
||||
currency: bool = False,
|
||||
currency_code: str = "EUR",
|
||||
decimals: int = 0,
|
||||
comparison_label: str = "vs n-1",
|
||||
extra_visualization_settings: dict | None = None,
|
||||
) -> dict:
|
||||
"""Construye el payload para POST /api/card de un KPI smartscalar.
|
||||
|
||||
Combina el SQL nativo (idealmente generado por
|
||||
``metabase_smartscalar_kpi_sql``) con la configuracion de visualizacion
|
||||
smartscalar que muestra ``valor`` con comparacion ``previousValue`` y
|
||||
formato numero/divisa. El payload resultante puede pasarse directamente
|
||||
a ``metabase_create_card_raw``.
|
||||
|
||||
Args:
|
||||
name: Nombre de la card.
|
||||
database_id: ID de la database de Metabase contra la que se ejecuta.
|
||||
sql: Query nativa que devuelve columnas ``periodo`` (DATE) y ``valor``
|
||||
(numero) con 2 filas (n-1, actual). Generable con
|
||||
``metabase_smartscalar_kpi_sql``.
|
||||
template_tags: Dict de template-tags para field-filters
|
||||
(``{tag_name: tag_dict}``). Construir con
|
||||
``metabase_smartscalar_dimension_tag``. None = sin filtros.
|
||||
description: Descripcion mostrada en la card. Vacio se autorrellena.
|
||||
collection_id: ID de la coleccion destino. 0 = root.
|
||||
currency: Si True formatea ``valor`` como moneda con ``currency_code``.
|
||||
currency_code: Codigo ISO de moneda. Default EUR.
|
||||
decimals: Decimales mostrados en ``valor``.
|
||||
comparison_label: Etiqueta del bloque de comparacion. Default "vs n-1".
|
||||
extra_visualization_settings: Settings adicionales a fusionar (top-level
|
||||
override) en ``visualization_settings``. Util para anadir
|
||||
``scalar.title``, ``card.title.alignment``, etc.
|
||||
|
||||
Returns:
|
||||
Dict con estructura:
|
||||
|
||||
{
|
||||
"name": ..., "description": ..., "type": "question",
|
||||
"display": "smartscalar",
|
||||
"dataset_query": {"database": ..., "type": "native",
|
||||
"native": {"query": ..., "template-tags": ...}},
|
||||
"visualization_settings": {
|
||||
"scalar.field": "valor",
|
||||
"scalar.comparisons": [
|
||||
{"id": "cmp_n1", "type": "previousValue", "label": ...}],
|
||||
"column_settings": {'["name","valor"]': {...formato...}}
|
||||
},
|
||||
["collection_id"]: ...
|
||||
}
|
||||
|
||||
Example:
|
||||
>>> sql = metabase_smartscalar_kpi_sql(
|
||||
... act_expr="ROUND(SUM(v.venta_n), 2)",
|
||||
... n1_expr="ROUND(SUM(v.venta_n1), 2)",
|
||||
... body_sql="FROM ventas v",
|
||||
... date_expr="MIN(v.fecha)",
|
||||
... )
|
||||
>>> tags = {"fecha": metabase_smartscalar_dimension_tag(
|
||||
... name="fecha", field_id=322392,
|
||||
... base_type="type/Date", widget_type="date/all-options",
|
||||
... )}
|
||||
>>> payload = metabase_smartscalar_kpi_payload(
|
||||
... name="Venta total", database_id=6, sql=sql,
|
||||
... template_tags=tags, currency=True, decimals=0,
|
||||
... )
|
||||
>>> assert payload["display"] == "smartscalar"
|
||||
>>> assert payload["visualization_settings"]["scalar.field"] == "valor"
|
||||
"""
|
||||
fmt: dict = {"decimals": decimals}
|
||||
if currency:
|
||||
fmt.update(
|
||||
{
|
||||
"number_style": "currency",
|
||||
"currency": currency_code,
|
||||
"currency_in_header": False,
|
||||
}
|
||||
)
|
||||
viz: dict = {
|
||||
"scalar.field": "valor",
|
||||
"scalar.comparisons": [
|
||||
{"id": "cmp_n1", "type": "previousValue", "label": comparison_label}
|
||||
],
|
||||
"column_settings": {'["name","valor"]': fmt},
|
||||
}
|
||||
if extra_visualization_settings:
|
||||
viz.update(extra_visualization_settings)
|
||||
|
||||
payload: dict = {
|
||||
"name": name,
|
||||
"description": description
|
||||
or f"KPI {name} con comparacion vs mismo periodo del ano anterior.",
|
||||
"type": "question",
|
||||
"display": "smartscalar",
|
||||
"dataset_query": {
|
||||
"database": database_id,
|
||||
"type": "native",
|
||||
"native": {
|
||||
"query": sql,
|
||||
"template-tags": template_tags or {},
|
||||
},
|
||||
},
|
||||
"visualization_settings": viz,
|
||||
}
|
||||
if collection_id > 0:
|
||||
payload["collection_id"] = collection_id
|
||||
return payload
|
||||
Reference in New Issue
Block a user