From 8d77a394a7b9c74076c0b2167a54aca1f0e65aa9 Mon Sep 17 00:00:00 2001 From: oxymus Date: Thu, 20 Aug 2026 09:53:21 -0400 Subject: [PATCH 1/2] =?UTF-8?q?feat[EDD-1243]=20-=20Criando=20an=C3=A1lise?= =?UTF-8?q?=20sobre=20viabilidade=20em=20aplicar=20dash=20abono=20permanen?= =?UTF-8?q?cia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...viabilidade-dashboard-abono-permanencia.md | 243 ++++++++++++++++++ ...iabilidade-dashboard-abono-permanencia.pdf | Bin 0 -> 66176 bytes 2 files changed, 243 insertions(+) create mode 100644 docs/internal/viabilidade-dashboard-abono-permanencia.md create mode 100644 docs/internal/viabilidade-dashboard-abono-permanencia.pdf diff --git a/docs/internal/viabilidade-dashboard-abono-permanencia.md b/docs/internal/viabilidade-dashboard-abono-permanencia.md new file mode 100644 index 0000000000..c273d8951c --- /dev/null +++ b/docs/internal/viabilidade-dashboard-abono-permanencia.md @@ -0,0 +1,243 @@ +# Viabilidade: dashboard “Abono de Permanência” no Metabase + +Análise do que o Metabase já cobre nativamente e do que precisaria ser implementado para aproximar os modelos solicitados (card composto de evolução e dashboard de quatro colunas). + +**Público:** time de produto/engenharia que vai adaptar o mockup ao Metabase. +**Escopo:** um dashboard por objeto validado (exemplo: Abono de Permanência), com título, filtro de período e quatro colunas na mesma linha. + +--- + +## Resposta direta + +A **estrutura** do dashboard (título, filtro de período, quatro colunas, clique para detalhar) dá para montar com recursos nativos. + +O mockup trata cada coluna como um **widget composto**: vários tipos de gráfico, ícones e layout interno no mesmo card. No Metabase, um card é **uma question** e **um tipo de visualização**. A adaptação realista é: + +- layout nativo + **várias questions por coluna**; +- implementação nova só onde o visual do mockup for obrigatório. + +Não existe plugin público para registrar um chart custom sem alterar o código. Dá para criar visualizações novas no frontend deste repositório, pelo mesmo caminho do Combo e do Scatter (`registerVisualization`). + +--- + +## O que o mockup pede + +### Cabeçalho + +- Título do objeto à esquerda (exemplo: **Abono de Permanência**). +- Seletor de período na mesma barra (mês/ano até mês/ano). +- O check de “aplicar” do mockup **não entra**: o Metabase aplica o filtro no próprio select. + +### Quatro colunas na mesma linha + +1. **Evolução:** KPIs em texto (valor total, média mensal, valor por beneficiário) + gráfico linha (R$ milhões) empilhado sobre barras (pessoas, mil), eixo X mensal compartilhado. +2. **Comparação e Top 3:** dois boxes (Analisado vs Divergências, este com fundo vermelho) + três mini barras (por motivo, órgão, mês). +3. **Análise de divergências:** scatter/bolhas com quadrantes, labels nas bolhas, clique na bolha abre o detalhe. +4. **Detalhe do órgão (Seduc):** lista hierárquica expansível (divergência financeira, pessoal afetado, média mensal) com tabelas internas e drill. + +### Card composto isolado (primeiro print) + +Um único card com lista de KPIs no topo e, abaixo, o bloco **EVOLUÇÃO** (linha + barras sincronizadas). Esse modelo **não** existe como um único tipo de question. + +--- + +## Como o Metabase organiza cards + +| Conceito | Comportamento nativo | +| --- | --- | +| Question / card | Um tipo de visualização (`combo`, `scalar`, `scatter`, `bar`, `pivot`, etc.) | +| Dashboard | Grade de **24 colunas** (`GRID_WIDTH`). Quatro colunas iguais = largura **6** cada | +| Largura | **Fixed width** (padrão) ou **Full width** (melhor para este layout denso) | +| Tipos de card no dashboard | Questions, heading, text (Markdown), link, iframe | +| Filtros | Dashboard inteiro, **heading** da aba, ou card individual | +| Várias séries | Na mesma question (várias métricas) ou sobrepondo questions no mesmo card do dashboard, se compartilharem a dimensão (tempo) | + +Documentação relacionada: + +- [Introduction to dashboards](../dashboards/introduction.md) +- [Dashboard filters](../dashboards/filters.md) +- [Charts with multiple series](../dashboards/multiple-series.md) +- [Combo charts](../questions/visualizations/combo-chart.md) +- [Dashboard interactivity](../dashboards/interactive.md) + +--- + +## Nativo vs implementar + +Legenda: **Nativo** = dá para usar hoje; **Parcial** = cobre o dado, não o visual; **Implementar** = exige código novo. + +### Cabeçalho e layout + +| Pedido | Status | Como fazer / o que falta | +| --- | --- | --- | +| Quatro blocos na mesma linha | **Nativo** | Grade 24 colunas; **Full width** | +| Título do objeto | **Nativo** | Heading card | +| Filtro de período (mês a mês) | **Nativo** | Date picker no **heading** (não no dashboard inteiro, se o filtro for só desta aba) | +| Aplicar com botão check | **Não usar** | O Metabase aplica no select | +| Título + date range no mesmo controle visual do print | **Parcial** | Heading + widget à direita. Não fica “colado” como no mockup | +| Uma moldura única por coluna envolvendo vários gráficos | **Implementar** | Nativo empilha cards soltos, cada um com a própria borda | + +**Implementar?** Não vale um componente só para o chrome do cabeçalho. O Date picker nativo cobre o comportamento. + +### Coluna 1 — Evolução (KPIs + linha + barras) + +| Pedido | Status | Como fazer / o que falta | +| --- | --- | --- | +| Linha (R$) + barras (pessoas) no mesmo eixo X | **Nativo** | Visualização **Combo**. Por série: line, bar ou area | +| Linha em cima, barras embaixo | **Nativo** | **Stack series** (`graph.split_panels`) | +| Escalas diferentes (milhões vs milhares) | **Nativo** | Split y-axis se as séries estiverem sobrepostas; painéis separados já isolam a escala | +| Labels nos pontos e nas barras | **Nativo** | **Show values on data points** | +| Duas ou mais métricas + agrupamento temporal | **Nativo** | 2+ métricas e 1–2 agrupamentos, ou 1 métrica e 2 agrupamentos | +| Três KPIs (total, média, por beneficiário) | **Parcial** | Três cards **Number** (`scalar`) ou **Trend** (`smartscalar`). Sem layout “label à esquerda / valor à direita” numa lista | +| KPIs + gráfico no **mesmo** card | **Implementar** | Uma question = um tipo de viz | +| Ícones `$` / pessoa no eixo | **Implementar** | Só texto de eixo | +| Ícone olho nos pontos | **Implementar** | Sem anotações custom em pontos | + +**Combo no código:** `frontend/src/metabase/visualizations/visualizations/ComboChart/`. +**Stack series:** setting `graph.split_panels` em `frontend/src/metabase/visualizations/lib/settings/graph.ts`. + +Seção nativa próxima: **KPIs with large chart below** em [dashboard sections](../dashboards/introduction.md#dashboard-sections) — KPIs e gráfico são cards separados, não um único widget. + +### Coluna 2 — Analisado vs Divergências + Top 3 + +| Pedido | Status | Como fazer / o que falta | +| --- | --- | --- | +| Métricas Analisado vs Divergências | **Parcial** | Vários cards **Number** / **Trend** | +| Cor condicional do número | **Nativo** | Number: aba **Conditional colors** | +| Fundo vermelho do box inteiro | **Implementar** | Condicional pinta o número, não o card | +| R$ + pessoas + % no mesmo box | **Implementar** | Um Number = um valor | +| Ícones nos KPIs | **Implementar** | | +| Título “Top 3 divergências” | **Nativo** | Heading ou text card | +| Ícones de ação (home, calendário, user, tag) | **Parcial** | Link cards ou click behavior; não há barra de ícones no header do card | +| Três mini barras lado a lado | **Nativo** | Três questions **Bar** (top 3 por motivo / órgão / mês) | + +### Coluna 3 — Scatter / bolhas com quadrantes + +| Pedido | Status | Como fazer / o que falta | +| --- | --- | --- | +| Scatter / bubble (X, Y, tamanho) | **Nativo** | **Scatter** + bubble size | +| Cor por categoria (órgão) | **Nativo** | Breakout | +| Clique na bolha → detalhe na coluna 4 | **Nativo** | **Click behavior** → **Update a dashboard filter** (órgão) ligado aos cards da coluna 4 | +| Uma linha de meta no Y | **Parcial** | Goal line; não desenha a cruz dos quatro quadrantes | +| Quadrantes (linhas tracejadas em X e Y) | **Implementar** | Sem `markLine` em ambos os eixos | +| Labels nas bolhas (“Seduc”) | **Implementar** | Tooltip e legenda; sem rótulo no ponto | + +Esta é a coluna que **mais se aproxima** do mockup só com nativo, se o time aceitar tooltip no lugar do label e sem quadrantes desenhados. + +**Scatter no código:** `frontend/src/metabase/visualizations/visualizations/ScatterPlot/`. +Docs: [Scatterplots and bubble charts](../questions/visualizations/scatterplot-or-bubble-chart.md). + +### Coluna 4 — Detalhe hierárquico do órgão + +| Pedido | Status | Como fazer / o que falta | +| --- | --- | --- | +| Título dinâmico (Secretaria da Educação) | **Nativo** | Heading com variável `{{orgao}}` ligada ao filtro | +| Breakdown com subtotais | **Nativo** | **Pivot table** (query builder): grupos, subtotais, expand/collapse | +| Tabelas (categoria, R$, %) | **Nativo** | Table ou Pivot | +| Lista de cargos + contagens | **Nativo** | Table ou **Row** chart | +| Linha “divergência média mensal” | **Nativo** | Number | +| Drill para outro dashboard / question | **Nativo** | Click behavior ou drill-through | +| Accordion com % e valor no header da seção | **Implementar** | Pivot expande grupos; não replica o visual de seções do mockup | + +**List view** (`list`) serve para explorar **registros** de um model, não para este breakdown analítico. Ver [Model list view](../data-modeling/models.md#model-list-view). + +Pivot: [Pivot tables](../questions/visualizations/pivot-table.md). + +--- + +## Montagem nativa sugerida (espelho funcional) + +``` +[ Heading: Abono de Permanência ] [ Date picker no heading ] +[ 3× Number + 1× Combo (stack) ] [ 4–6× Number + 3× Bar ] +[ 1× Scatter / Bubble ] [ Heading {{orgao}} + Pivot / Tables ] +``` + +Interação: clique na bolha **Seduc** → atualiza filtro de órgão → coluna 4 recalcula. Isso já existe e é o equivalente nativo do “detalhamento ao clicar”. + +Sobrepor questions no mesmo card do dashboard só ajuda quando as séries compartilham o eixo de tempo (evolução). Não resolve KPIs + gráfico, nem boxes + mini barras, nem scatter + accordion. + +--- + +## O que está além do nativo (lista fechada) + +1. Vários tipos de gráfico **no mesmo card** (KPI + combo; dois boxes + três bars). +2. Chrome visual: moldura única da coluna, fundo vermelho do bloco, ícones de unidade/ação, olho nos pontos. +3. Quadrantes + labels em bolhas. +4. Accordion analítico com métricas no header da seção. +5. Filtro de período no pixel do mockup (dois dropdowns + calendário + check na mesma barra do título). + +Os `PLUGIN_*` do produto são para features EE (SSO, embedding SDK, etc.). **Não** há hook para registrar um chart de dashboard sem mudar o código. + +--- + +## Dá para implementar componentes novos? + +Sim. O registro de visualizações é de primeira classe neste repositório. Não é um plugin externo: é código no frontend. + +### Caminho padrão (igual Combo, Scatter, Pivot) + +1. Novo identificador em `cardDisplayTypes` — `frontend/src/metabase-types/api/visualization.ts`. +2. Componente React com `VisualizationDefinition`: `identifier`, `getUiName`, `isSensible`, `checkRenderable`, `settings`, tamanhos min/default. +3. `registerVisualization(...)` em `frontend/src/metabase/visualizations/register.js`. +4. Settings no painel de viz (mesmo padrão de `graph.split_panels`). +5. Se houver **subscriptions / export PNG / static viz**, espelhar em `frontend/src/metabase/static-viz/`. Sem isso, e-mail/Slack saem quebrados ou sem o chart. +6. Testes (Jest + E2E), i18n com `ttag`, e tamanhos em `metabase/visualizations/shared/utils/sizes`. + +O novo tipo entra no query builder, no dashboard, no visualizer e no embedding, como qualquer chart nativo. + +### Três níveis de customização + +| Nível | O que é | Esforço | Quando usar | +| --- | --- | --- | --- | +| **A. Só nativo** | Heading + filtro + grade de 4 colunas + várias questions | Baixo | Entregar o dado rápido; aceitar visual Metabase | +| **B. Novos viz no core** | 1–3 displays novos (scatter com quadrantes; KPI+combo; comparison panel; accordion) | Médio a alto | Reuso em vários dashboards / objetos | +| **C. Iframe / app ao lado** | Card iframe com app React próprio; filtros via `{{variáveis}}` no `src` | Médio, paralelo ao core | Pixel-perfect de um relatório, sem carregar o produto | + +Iframe: [Iframe cards](../dashboards/introduction.md#iframe-cards). O host precisa estar em `allowed-iframe-hosts`. + +### Candidatos a viz novos (se o time for para o nível B) + +| Componente | Coluna | Esforço | Valor | +| --- | --- | --- | --- | +| Scatter com `markLine` nos dois eixos + label no ponto | 3 | Baixo a médio | Alto: o modelo de dados já existe | +| `kpi-combo` (lista de métricas + Combo com split panels) | 1 | Médio a alto | Alto só se for template de vários objetos | +| `comparison-panel` (dois boxes + métricas + cor de fundo) | 2 | Médio | Médio | +| `mini-multi-bar` (três top-N no mesmo card) | 2 | Baixo a médio | Baixo: três Bar nativos já resolvem | +| `breakdown-accordion` | 4 | Médio a alto | Médio: Pivot já entrega o dado | + +--- + +## Recomendação + +1. **Começar no nível A:** heading + date filter, quatro colunas, Combo com **Stack series**, Numbers, Bars, Scatter, Pivot/Tables, click behavior da bolha para o filtro de órgão. +2. **Customizar só a coluna 3** (quadrantes + labels) se o gráfico de divergências for o centro da análise — melhor custo/benefício. +3. **Viz compostos nas colunas 1, 2 e 4** só se este layout for **template** para muitos objetos (Abono, Licença, etc.), não um dashboard único. +4. **Não implementar** o check do filtro nem ícones decorativos nos eixos, a menos que o pixel-perfect seja requisito contratual. Aí o caminho é nível B (viz novos) ou C (iframe). + +--- + +## Protótipo nativo (dados mock) + +Há um seed pronto para apresentar no Metabase local, só com recursos nativos: + +- Pasta: [`docs/internal/abono-demo/`](abono-demo/README.md) +- Script: `python3 docs/internal/abono-demo/seed_abono_demo.py --email ... --password ...` + +## Referências no código + +| Tema | Caminho | +| --- | --- | +| Largura da grade | `frontend/src/metabase/lib/dashboard_grid.js` (`GRID_WIDTH = 24`) | +| Tipos de display | `frontend/src/metabase-types/api/visualization.ts` | +| Registro de viz | `frontend/src/metabase/visualizations/register.js` | +| API de viz | `frontend/src/metabase/visualizations/index.ts` (`registerVisualization`) | +| Combo | `frontend/src/metabase/visualizations/visualizations/ComboChart/` | +| Scatter | `frontend/src/metabase/visualizations/visualizations/ScatterPlot/` | +| Number | `frontend/src/metabase/visualizations/visualizations/Scalar/` | +| Trend | `frontend/src/metabase/visualizations/visualizations/SmartScalar/` | +| Pivot | `frontend/src/metabase/visualizations/visualizations/PivotTable/` | +| Heading / iframe | `.../Heading/`, `.../IFrameViz/` | +| Stack series / split y-axis | `frontend/src/metabase/visualizations/lib/settings/graph.ts` | +| Seções de dashboard | `frontend/src/metabase/dashboard/sections.ts` | +| Static viz (PNG / subscriptions) | `frontend/src/metabase/static-viz/` | diff --git a/docs/internal/viabilidade-dashboard-abono-permanencia.pdf b/docs/internal/viabilidade-dashboard-abono-permanencia.pdf new file mode 100644 index 0000000000000000000000000000000000000000..adcbc18b3fc8e8fbfd5c6c74cc589eb0625a0b8f GIT binary patch literal 66176 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 Date: Fri, 21 Aug 2026 08:52:54 -0400 Subject: [PATCH 2/2] feat[EDD-1243] - Mock e estrutura para abono permanencia nos arquivos nativos --- docs/internal/abono-demo/README.md | 69 + docs/internal/abono-demo/build_sqlite.py | 218 ++ .../dash_10_backup_20260821_083836.json | 3191 +++++++++++++++++ docs/internal/abono-demo/seed_abono_demo.py | 824 +++++ 4 files changed, 4302 insertions(+) create mode 100644 docs/internal/abono-demo/README.md create mode 100644 docs/internal/abono-demo/build_sqlite.py create mode 100644 docs/internal/abono-demo/dash_10_backup_20260821_083836.json create mode 100644 docs/internal/abono-demo/seed_abono_demo.py diff --git a/docs/internal/abono-demo/README.md b/docs/internal/abono-demo/README.md new file mode 100644 index 0000000000..b8c35729f9 --- /dev/null +++ b/docs/internal/abono-demo/README.md @@ -0,0 +1,69 @@ +# Dashboard demo: Abono de Permanência + +Protótipo **nativo** (nível A da [análise de viabilidade](../viabilidade-dashboard-abono-permanencia.md)) para apresentar no seu Metabase local. Os dados são mock (jul–dez/2025) e não falam com a base do cliente. + +## O que o seed cria + +- Banco SQLite `abono_permanencia.sqlite` com duas tabelas: `abono_folha` e `abono_divergencia` +- Coleção **Abono de Permanência (demo)** +- Dashboard em largura full, quatro colunas na mesma linha: + +| Coluna | Cards nativos | +| --- | --- | +| Cabeçalho | Heading **Abono de Permanência** + filtro **Período** (`date/all-options`, padrão `2025-07-01~2025-12-31`) | +| 1. Evolução | 3 Numbers + Combo (linha de valor / barras de beneficiários, **Stack series**) | +| 2. Comparação + Top 3 | Numbers Analisado vs Divergências (valor, pessoas, %) + 3 Bars | +| 3. Scatter | Bolhas financeira × normativa × pessoas. Clique atualiza o órgão | +| 4. Detalhe | Heading `{{orgao}}` (padrão Seduc) + tabelas motivo/cargo + Number da média mensal | + +O check de apply do mockup não entra: o Metabase aplica o período no select. + +## Subir no seu ambiente + +1. Metabase rodando e **setup já feito**, com um usuário **admin**. +2. Na raiz do repositório: + +``` +python3 docs/internal/abono-demo/seed_abono_demo.py \ + --url http://localhost:3000 \ + --email SEU_ADMIN@empresa.com \ + --password 'sua-senha' +``` + +Ou via ambiente: `MB_URL`, `MB_EMAIL`, `MB_PASSWORD`. + +3. Abra a URL que o script imprimir (`/dashboard/`), ou a coleção **Abono de Permanência (demo)**. + +Rodar de novo arquiva o dashboard anterior com o mesmo nome e cria outro. O SQLite é recriado a cada execução. + +Só gerar o arquivo, sem API: + +``` +python3 docs/internal/abono-demo/seed_abono_demo.py --sqlite-only +``` + +## Docker + +O Metabase precisa **enxergar o caminho do SQLite**. Se o app roda num container, monte a pasta e aponte o banco para o path **dentro** do container, por exemplo `/data/abono_permanencia.sqlite`. Depois ajuste Admin > Databases ou rode o seed a partir de um ambiente que use esse mesmo path. + +No WSL / jar / `clojure -M:dev` na mesma máquina, o path absoluto gerado pelo script costuma funcionar. + +## Totais do mock (jul–dez/2025) + +- Valor total: **R$ 300.979.598,94** +- Valor mensal médio: **R$ 50.163.266,49** +- Valor por beneficiário/mês: **R$ 1.967,18** +- Divergências: **12%** da folha (o restante é “analisado”) +- Órgãos: Seduc (maior bolha), SES, SSP, SEFAZ, SEINFRA, DETRAN + +## O que ainda não é nativo neste protótipo + +Deixado de propósito para a próxima etapa: + +- KPIs + Combo no **mesmo** card +- Fundo vermelho no box inteiro de Divergências (só a cor do número) +- Ícones de unidade, olho nos pontos, quadrantes e labels nas bolhas +- Accordion com % no header da coluna 4 +- Uma moldura única por coluna + +Clique na bolha **Seduc** (ou outro órgão) para ver o detalhe da coluna 4 mudar. diff --git a/docs/internal/abono-demo/build_sqlite.py b/docs/internal/abono-demo/build_sqlite.py new file mode 100644 index 0000000000..1058145c7f --- /dev/null +++ b/docs/internal/abono-demo/build_sqlite.py @@ -0,0 +1,218 @@ +"""Build the SQLite mock used by the Abono de Permanência demo dashboard.""" + +from __future__ import annotations + +import sqlite3 +from pathlib import Path + +DB_NAME = "abono_permanencia.sqlite" + +# Totals from the mockup: R$ 300.979.598,94 / média R$ 50.163.266,49 / R$ 1.967,18 por beneficiário. +FOLHA = [ + # competencia, valor_total, qtd_beneficiarios + ("2025-07-01", 50_100_000.00, 25_500), + ("2025-08-01", 51_300_000.00, 25_800), + ("2025-09-01", 52_700_000.00, 26_200), + ("2025-10-01", 49_800_000.00, 25_200), + ("2025-11-01", 49_079_598.94, 25_100), + ("2025-12-01", 48_000_000.00, 25_200), +] + +ORGAOS = [ + ("Seduc", 0.45, 78.4), + ("SES", 0.18, 41.2), + ("SSP", 0.12, 55.0), + ("SEFAZ", 0.10, 22.5), + ("SEINFRA", 0.08, 33.8), + ("DETRAN", 0.07, 18.1), +] + +MOTIVOS = [ + ("Pagamento a maior", 0.40), + ("Tempo de serviço irregular", 0.25), + ("Acumulação indevida", 0.18), + ("Cargo incompatível", 0.12), + ("Outros", 0.05), +] + +CARGOS_POR_ORGAO = { + "Seduc": [ + ("Professor", 0.62), + ("Agente administrativo", 0.22), + ("Diretor escolar", 0.16), + ], + "SES": [ + ("Técnico de enfermagem", 0.48), + ("Médico", 0.32), + ("Agente administrativo", 0.20), + ], + "SSP": [ + ("Policial civil", 0.55), + ("Escrivão", 0.28), + ("Agente administrativo", 0.17), + ], + "SEFAZ": [ + ("Auditor fiscal", 0.50), + ("Analista fazendário", 0.30), + ("Agente administrativo", 0.20), + ], + "SEINFRA": [ + ("Engenheiro", 0.42), + ("Técnico em edificações", 0.35), + ("Agente administrativo", 0.23), + ], + "DETRAN": [ + ("Examinador de trânsito", 0.46), + ("Agente de trânsito", 0.34), + ("Agente administrativo", 0.20), + ], +} + +# Share of payroll flagged as divergence (rest is "analisado"). +DIVERGENCIA_SHARE = 0.12 + + +def db_path(base_dir: Path | None = None) -> Path: + root = base_dir or Path(__file__).resolve().parent + return root / DB_NAME + + +def _create_schema(conn: sqlite3.Connection) -> None: + conn.executescript( + """ + DROP TABLE IF EXISTS abono_folha; + DROP TABLE IF EXISTS abono_divergencia; + + CREATE TABLE abono_folha ( + competencia DATE NOT NULL, + valor_total REAL NOT NULL, + qtd_beneficiarios INTEGER NOT NULL, + valor_analisado REAL NOT NULL, + qtd_analisado INTEGER NOT NULL, + valor_divergencias REAL NOT NULL, + qtd_divergencias INTEGER NOT NULL + ); + + CREATE TABLE abono_divergencia ( + competencia DATE NOT NULL, + orgao TEXT NOT NULL, + motivo TEXT NOT NULL, + cargo TEXT NOT NULL, + valor REAL NOT NULL, + qtd_pessoas INTEGER NOT NULL, + score_normativa REAL NOT NULL + ); + """ + ) + + +def _build_rows() -> tuple[list[tuple], list[tuple]]: + folha_rows: list[tuple] = [] + divergencia_rows: list[tuple] = [] + + for competencia, valor_total, qtd_beneficiarios in FOLHA: + valor_div = round(valor_total * DIVERGENCIA_SHARE, 2) + valor_ok = round(valor_total - valor_div, 2) + qtd_div = int(round(qtd_beneficiarios * DIVERGENCIA_SHARE)) + qtd_ok = qtd_beneficiarios - qtd_div + folha_rows.append( + ( + competencia, + valor_total, + qtd_beneficiarios, + valor_ok, + qtd_ok, + valor_div, + qtd_div, + ) + ) + + remaining_valor = valor_div + remaining_pessoas = qtd_div + org_items = list(enumerate(ORGAOS)) + for org_i, (orgao, org_w, score) in org_items: + last_org = org_i == len(org_items) - 1 + org_valor = remaining_valor if last_org else round(valor_div * org_w, 2) + org_pessoas = remaining_pessoas if last_org else int(round(qtd_div * org_w)) + remaining_valor = round(remaining_valor - org_valor, 2) + remaining_pessoas -= org_pessoas + + mot_remaining_v = org_valor + mot_remaining_p = org_pessoas + mot_items = list(enumerate(MOTIVOS)) + cargos = CARGOS_POR_ORGAO[orgao] + for mot_i, (motivo, mot_w) in mot_items: + last_mot = mot_i == len(mot_items) - 1 + mot_valor = mot_remaining_v if last_mot else round(org_valor * mot_w, 2) + mot_pessoas = mot_remaining_p if last_mot else int(round(org_pessoas * mot_w)) + mot_remaining_v = round(mot_remaining_v - mot_valor, 2) + mot_remaining_p -= mot_pessoas + + cargo_remaining_v = mot_valor + cargo_remaining_p = mot_pessoas + cargo_items = list(enumerate(cargos)) + for cargo_i, (cargo, cargo_w) in cargo_items: + last_cargo = cargo_i == len(cargo_items) - 1 + cargo_valor = ( + cargo_remaining_v if last_cargo else round(mot_valor * cargo_w, 2) + ) + cargo_pessoas = ( + cargo_remaining_p if last_cargo else max(1, int(round(mot_pessoas * cargo_w))) + ) + if cargo_pessoas > cargo_remaining_p: + cargo_pessoas = cargo_remaining_p + cargo_remaining_v = round(cargo_remaining_v - cargo_valor, 2) + cargo_remaining_p -= cargo_pessoas + if cargo_valor <= 0 and cargo_pessoas <= 0: + continue + divergencia_rows.append( + ( + competencia, + orgao, + motivo, + cargo, + cargo_valor, + max(cargo_pessoas, 0), + score, + ) + ) + + return folha_rows, divergencia_rows + + +def build(base_dir: Path | None = None) -> Path: + path = db_path(base_dir) + path.parent.mkdir(parents=True, exist_ok=True) + if path.exists(): + path.unlink() + + folha_rows, divergencia_rows = _build_rows() + conn = sqlite3.connect(path) + try: + _create_schema(conn) + conn.executemany( + """ + INSERT INTO abono_folha ( + competencia, valor_total, qtd_beneficiarios, + valor_analisado, qtd_analisado, valor_divergencias, qtd_divergencias + ) VALUES (?, ?, ?, ?, ?, ?, ?) + """, + folha_rows, + ) + conn.executemany( + """ + INSERT INTO abono_divergencia ( + competencia, orgao, motivo, cargo, valor, qtd_pessoas, score_normativa + ) VALUES (?, ?, ?, ?, ?, ?, ?) + """, + divergencia_rows, + ) + conn.commit() + finally: + conn.close() + return path + + +if __name__ == "__main__": + out = build() + print(f"SQLite gerado em {out}") diff --git a/docs/internal/abono-demo/dash_10_backup_20260821_083836.json b/docs/internal/abono-demo/dash_10_backup_20260821_083836.json new file mode 100644 index 0000000000..5a47e2a23d --- /dev/null +++ b/docs/internal/abono-demo/dash_10_backup_20260821_083836.json @@ -0,0 +1,3191 @@ +{ + "description": "Protótipo nativo (nível A). Dados mock de jul–dez/2025. Clique numa bolha para filtrar o detalhe do órgão.", + "archived": false, + "view_count": 9, + "collection_position": null, + "dashcards": [ + { + "size_x": 24, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [ + "a1b2c3d4" + ], + "action_id": null, + "collection_authority_level": null, + "card": { + "query_average_duration": null, + "download_perms": "none" + }, + "updated_at": "2026-08-17T12:35:56.616049Z", + "col": 0, + "id": 102, + "parameter_mappings": [], + "card_id": null, + "entity_id": "brZlcw2EWDBnuvGBD8s9i", + "visualization_settings": { + "text": "", + "virtual_card": { + "name": null, + "display": "heading", + "visualization_settings": {}, + "archived": false + }, + "dashcard.background": false + }, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 0 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "query_average_duration": null, + "download_perms": "none" + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 12, + "id": 103, + "parameter_mappings": [], + "card_id": null, + "entity_id": "l0HGOZWVi-RhU4MpVdfKc", + "visualization_settings": { + "text": "Análise / detalhamento divergências", + "virtual_card": { + "name": null, + "display": "heading", + "visualization_settings": {}, + "archived": false + }, + "dashcard.background": false + }, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 2 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [ + "b2c3d4e5" + ], + "action_id": null, + "collection_authority_level": null, + "card": { + "query_average_duration": null, + "download_perms": "none" + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 18, + "id": 104, + "parameter_mappings": [ + { + "parameter_id": "b2c3d4e5", + "target": [ + "text-tag", + "orgao" + ] + } + ], + "card_id": null, + "entity_id": "RTJ26Pq7RuG5a5soCclh7", + "visualization_settings": { + "text": "{{orgao}}", + "virtual_card": { + "name": null, + "display": "heading", + "visualization_settings": {}, + "archived": false + }, + "dashcard.background": false + }, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 2 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "query_average_duration": null, + "download_perms": "none" + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 6, + "id": 105, + "parameter_mappings": [], + "card_id": null, + "entity_id": "eXpua-WEULEOPNXe88i96", + "visualization_settings": { + "text": "Top 3 divergências", + "virtual_card": { + "name": null, + "display": "heading", + "visualization_settings": {}, + "archived": false + }, + "dashcard.background": false + }, + "size_y": 1, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 9 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "sum", + "lib/original-name": "sum", + "name": "sum", + "lib/source": "source/aggregations", + "lib/source-column-alias": "sum", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "sum", + "display_name": "Sum of Valor Total", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Valor total", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:21.792882Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "sum", + { + "lib/uuid": "354889ad-77ee-4351-9a23-b8479b789d0e" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "fb453e09-dd1c-44c9-9632-09558f28d133", + "effective-type": "type/Float" + }, + 1611 + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 119, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "QYx6xurEeEEk8z-MkV5s4", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"sum\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:21.792882Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 0, + "id": 106, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 119, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 119, + "entity_id": "HiTuxoXffv_0Sw42fwvo6", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 2 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "media", + "lib/original-name": "media", + "name": "media", + "lib/source": "source/aggregations", + "lib/source-column-alias": "media", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "media", + "display_name": "media", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Valor mensal médio", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:22.271418Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "avg", + { + "name": "media", + "lib/uuid": "c486c6f8-4493-4e5c-80e5-cf2aae7fd622", + "display-name": "media" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "ef158ea2-2a79-4743-905a-bcecb610f4be", + "effective-type": "type/Float" + }, + 1611 + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 120, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "wa70fCexoxI0kEmj-iWhH", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"media\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:22.271418Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 0, + "id": 107, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 120, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 120, + "entity_id": "WwUHkSfLoQqahCEDy7CPh", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 4 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "lib/deduplicated-name": "per_capita", + "lib/original-name": "per_capita", + "name": "per_capita", + "lib/source": "source/aggregations", + "lib/source-column-alias": "per_capita", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "per_capita", + "display_name": "Valor por beneficiário/mês", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Valor por beneficiário/mês", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:22.458681Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "/", + { + "name": "per_capita", + "lib/uuid": "7ed3f1e9-2b29-4add-8b13-2cea2e2f4d03", + "display-name": "Valor por beneficiário/mês" + }, + [ + "sum", + { + "lib/uuid": "d69d5c53-c484-4e7a-bc27-aba25d587a56" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "49e0793b-e532-4445-91f9-b210211c4661", + "effective-type": "type/Float" + }, + 1611 + ] + ], + [ + "sum", + { + "lib/uuid": "3c86265b-864c-4d2a-8d93-7758dc168168" + }, + [ + "field", + { + "base-type": "type/Integer", + "lib/uuid": "8f61575b-4254-4e39-a89f-e53470426c7f", + "effective-type": "type/Integer" + }, + 1612 + ] + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 121, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "qIfqVhu1PUOB288WCLWju", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"per_capita\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:22.458681Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 0, + "id": 108, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 121, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 121, + "entity_id": "5dfVCfTGlngoMnFMgQGHj", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 6 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "database_type": "DATE", + "semantic_type": "type/CreationDate", + "table_id": 171, + "lib/temporal-unit": "month", + "lib/deduplicated-name": "competencia", + "lib/original-name": "competencia", + "lib/breakout?": true, + "unit": "month", + "name": "competencia", + "lib/source": "source/table-defaults", + "lib/source-column-alias": "competencia", + "lib/original-display-name": "Competencia", + "source": "breakout", + "field_ref": [ + "field", + 1610, + { + "temporal-unit": "month" + } + ], + "lib/transformation-added-base-type": true, + "effective_type": "type/Date", + "active": true, + "id": 1610, + "lib/desired-column-alias": "competencia", + "position": 0, + "visibility_type": "normal", + "inherited_temporal_unit": "month", + "display_name": "Competencia: Month", + "fingerprint": { + "global": { + "distinct-count": 6, + "nil%": 0.0 + }, + "type": { + "type/DateTime": { + "earliest": "2025-07-01", + "latest": "2025-12-01" + } + } + }, + "base_type": "type/Date" + }, + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "Valor total", + "lib/original-name": "Valor total", + "name": "Valor total", + "lib/source": "source/aggregations", + "lib/source-column-alias": "Valor total", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "Valor total", + "display_name": "Valor total", + "base_type": "type/Float" + }, + { + "lib/deduplicated-name": "Beneficiários", + "lib/original-name": "Beneficiários", + "name": "Beneficiários", + "lib/source": "source/aggregations", + "lib/source-column-alias": "Beneficiários", + "source": "aggregation", + "field_ref": [ + "aggregation", + 1 + ], + "effective_type": "type/Integer", + "lib/desired-column-alias": "Beneficiários", + "display_name": "Beneficiários", + "base_type": "type/Integer" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Evolução", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:22.643422Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "aggregation": [ + [ + "sum", + { + "name": "Valor total", + "lib/uuid": "4f60efc8-7e28-47b3-8b27-5290ed326cf4", + "display-name": "Valor total" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "f50f240a-fcff-4dd2-886e-8a0833f6f4b4", + "effective-type": "type/Float" + }, + 1611 + ] + ], + [ + "sum", + { + "name": "Beneficiários", + "lib/uuid": "b1f22f48-18ef-4661-b30a-587ec83f25bf", + "display-name": "Beneficiários" + }, + [ + "field", + { + "base-type": "type/Integer", + "lib/uuid": "c5c7c656-14e9-4395-9a70-1df271c0f850", + "effective-type": "type/Integer" + }, + 1612 + ] + ] + ], + "lib/type": "mbql.stage/mbql", + "source-table": 171, + "breakout": [ + [ + "field", + { + "base-type": "type/Date", + "temporal-unit": "month", + "lib/uuid": "cbf76b04-3bba-48d7-94a4-414975899fa8", + "effective-type": "type/Date" + }, + 1610 + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 122, + "legacy_query": null, + "parameter_mappings": [], + "display": "combo", + "archived_directly": false, + "entity_id": "kkxSj1qm8sT4mXHct8txX", + "collection_preview": true, + "visualization_settings": { + "graph.show_values": true, + "graph.x_axis.labels_enabled": false, + "graph.label_value_frequency": "all", + "graph.metrics": [ + "Valor total", + "Beneficiários" + ], + "graph.label_value_formatting": "compact", + "column_settings": { + "[\"name\",\"Valor total\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 1 + } + }, + "series_settings": { + "Valor total": { + "display": "line", + "color": "#509EE3", + "line.marker_enabled": true + }, + "Beneficiários": { + "display": "bar", + "color": "#7BBCE7" + } + }, + "graph.dimensions": [ + "competencia" + ], + "graph.split_panels": true + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:22.643422Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 0, + "id": 109, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 122, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 122, + "entity_id": "wY0HVUQoxpsxk1r7qXVB0", + "visualization_settings": {}, + "size_y": 8, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 8 + }, + { + "size_x": 3, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "sum", + "lib/original-name": "sum", + "name": "sum", + "lib/source": "source/aggregations", + "lib/source-column-alias": "sum", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "sum", + "display_name": "Sum of Valor Analisado", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Analisado — valor", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:22.799833Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "sum", + { + "lib/uuid": "1c7db934-6881-45fc-9ba6-bda4473dfd2b" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "207b842f-23db-47e6-8d18-6487dac086b7", + "effective-type": "type/Float" + }, + 1613 + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 123, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "tbT9jaLBqvrlxtRU0uPPO", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"sum\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:22.799833Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 6, + "id": 110, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 123, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 123, + "entity_id": "60ML78TqpDBQVuI3Ur9Uw", + "visualization_settings": {}, + "size_y": 3, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 2 + }, + { + "size_x": 3, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "sum", + "lib/original-name": "sum", + "name": "sum", + "lib/source": "source/aggregations", + "lib/source-column-alias": "sum", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "sum", + "display_name": "Sum of Valor Divergencias", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Divergências — valor", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:22.918803Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "sum", + { + "lib/uuid": "c0cf7fc4-69f4-4f9c-aa14-c160c65cd744" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "55f76a66-ab67-4dd2-8b3a-ec01ad8bf06c", + "effective-type": "type/Float" + }, + 1615 + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 124, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "iuEkiUctkJECWQObpq6u9", + "collection_preview": true, + "visualization_settings": { + "scalar.segments": [ + { + "min": 0, + "max": null, + "color": "#E75C58", + "label": "Divergência" + } + ], + "column_settings": { + "[\"name\",\"sum\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:22.918803Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 9, + "id": 111, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 124, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 124, + "entity_id": "u-776DPLCiX8bf0PRmBq5", + "visualization_settings": {}, + "size_y": 3, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 2 + }, + { + "size_x": 3, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "lib/deduplicated-name": "pessoas", + "lib/original-name": "pessoas", + "name": "pessoas", + "lib/source": "source/aggregations", + "lib/source-column-alias": "pessoas", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Integer", + "lib/desired-column-alias": "pessoas", + "display_name": "pessoas", + "base_type": "type/Integer" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Analisado — pessoas", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.04302Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "sum", + { + "name": "pessoas", + "lib/uuid": "e3bd3407-4437-4ab0-b32c-d82f46668a0a", + "display-name": "pessoas" + }, + [ + "field", + { + "base-type": "type/Integer", + "lib/uuid": "e0092ad9-8cdc-4bea-894f-2bfc942e4045", + "effective-type": "type/Integer" + }, + 1614 + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 125, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "2-GfZDVOhbOfRHVSQD6gG", + "collection_preview": true, + "visualization_settings": {}, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.04302Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 6, + "id": 112, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 125, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 125, + "entity_id": "K7rs3EtLRIhlRSA-X2C99", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 5 + }, + { + "size_x": 3, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "lib/deduplicated-name": "pessoas", + "lib/original-name": "pessoas", + "name": "pessoas", + "lib/source": "source/aggregations", + "lib/source-column-alias": "pessoas", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Integer", + "lib/desired-column-alias": "pessoas", + "display_name": "pessoas", + "base_type": "type/Integer" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Divergências — pessoas", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.162083Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "sum", + { + "name": "pessoas", + "lib/uuid": "5c71bcdb-afc1-4b2c-85d6-49fa6bce22a7", + "display-name": "pessoas" + }, + [ + "field", + { + "base-type": "type/Integer", + "lib/uuid": "fe3b4557-e680-4808-a851-c0d9591cc06d", + "effective-type": "type/Integer" + }, + 1616 + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 126, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "dHvoxjnhXJIt6PJh8xLtm", + "collection_preview": true, + "visualization_settings": { + "scalar.segments": [ + { + "min": 0, + "max": null, + "color": "#E75C58", + "label": "Divergência" + } + ] + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.162083Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:33:25.109933Z", + "col": 9, + "id": 113, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 126, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 126, + "entity_id": "hos45pp1AJz8bzWZJUU67", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 5 + }, + { + "size_x": 3, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "lib/deduplicated-name": "pct", + "lib/original-name": "pct", + "name": "pct", + "lib/source": "source/aggregations", + "lib/source-column-alias": "pct", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "pct", + "display_name": "% analisado", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Analisado — % do valor", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.29242Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "/", + { + "name": "pct", + "lib/uuid": "e85038c8-9eac-4878-93ff-e5d568c4ff58", + "display-name": "% analisado" + }, + [ + "sum", + { + "lib/uuid": "fc70a984-aee5-4298-aabf-598aa22a5166" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "60fce310-ce23-46c6-86c6-198c35499df7", + "effective-type": "type/Float" + }, + 1613 + ] + ], + [ + "sum", + { + "lib/uuid": "3471170d-e9b5-4491-9d9c-1b3abffba0ee" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "eeedae75-3b1e-479b-877d-ed58d81fa73b", + "effective-type": "type/Float" + }, + 1611 + ] + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 127, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "iQ72jYHDj4uP93fdhAeCn", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"pct\"]": { + "number_style": "percent", + "decimals": 1 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.29242Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 6, + "id": 114, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 127, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 127, + "entity_id": "M99R-NOXXA1RX9HoJSXBF", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 7 + }, + { + "size_x": 3, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 171, + "result_metadata": [ + { + "lib/deduplicated-name": "pct", + "lib/original-name": "pct", + "name": "pct", + "lib/source": "source/aggregations", + "lib/source-column-alias": "pct", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "pct", + "display_name": "% divergências", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Divergências — % do valor", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.41191Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "source-table": 171, + "lib/type": "mbql.stage/mbql", + "aggregation": [ + [ + "/", + { + "name": "pct", + "lib/uuid": "97a37d35-4605-466d-905c-cf3ed2c66c52", + "display-name": "% divergências" + }, + [ + "sum", + { + "lib/uuid": "e8898168-18fd-4cf2-bb3c-22220df841f7" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "d4e4b11b-53e3-4678-b7c7-d42da417901d", + "effective-type": "type/Float" + }, + 1615 + ] + ], + [ + "sum", + { + "lib/uuid": "55d3b288-d36b-4798-b402-dff0d9015b43" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "d295443a-bb22-49b7-9f1e-8645754a5b4f", + "effective-type": "type/Float" + }, + 1611 + ] + ] + ] + ] + } + ] + }, + "dimension_mappings": null, + "id": 128, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "PmSxAO2nF0-OWjiwswqvS", + "collection_preview": true, + "visualization_settings": { + "scalar.segments": [ + { + "min": 0, + "max": null, + "color": "#E75C58", + "label": "Divergência" + } + ], + "column_settings": { + "[\"name\",\"pct\"]": { + "number_style": "percent", + "decimals": 1 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.41191Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 9, + "id": 115, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 128, + "target": [ + "dimension", + [ + "field", + 1610, + null + ] + ] + } + ], + "card_id": 128, + "entity_id": "iE-1mUvyNFBtuTMkMNlyD", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 7 + }, + { + "size_x": 2, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 172, + "result_metadata": [ + { + "database_type": "TEXT", + "semantic_type": "type/Category", + "table_id": 172, + "lib/deduplicated-name": "motivo", + "lib/original-name": "motivo", + "lib/breakout?": true, + "name": "motivo", + "lib/source": "source/table-defaults", + "lib/source-column-alias": "motivo", + "lib/original-display-name": "Motivo", + "source": "breakout", + "field_ref": [ + "field", + 1619, + null + ], + "lib/transformation-added-base-type": true, + "effective_type": "type/Text", + "active": true, + "id": 1619, + "lib/desired-column-alias": "motivo", + "position": 2, + "visibility_type": "normal", + "display_name": "Motivo", + "fingerprint": { + "global": { + "distinct-count": 5, + "nil%": 0.0 + }, + "type": { + "type/Text": { + "percent-json": 0.0, + "percent-url": 0.0, + "percent-email": 0.0, + "percent-state": 0.0, + "average-length": 17.2 + } + } + }, + "base_type": "type/Text" + }, + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "valor", + "lib/original-name": "valor", + "name": "valor", + "lib/source": "source/aggregations", + "lib/source-column-alias": "valor", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "valor", + "display_name": "valor", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Top 3 por motivo", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.547811Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "order-by": [ + [ + "desc", + { + "lib/uuid": "78b5c760-1de5-4d1a-9eda-cfaf35e233f1" + }, + [ + "aggregation", + { + "lib/uuid": "7b40b3d3-d001-4781-a8f6-e70e7941eabd" + }, + "1cc93080-89b8-4b5e-bc58-05d70e85615f" + ] + ] + ], + "lib/type": "mbql.stage/mbql", + "source-table": 172, + "aggregation": [ + [ + "sum", + { + "name": "valor", + "lib/uuid": "1cc93080-89b8-4b5e-bc58-05d70e85615f", + "display-name": "valor" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "d13a7a4a-cad5-4050-b7fb-32a409b34580", + "effective-type": "type/Float" + }, + 1621 + ] + ] + ], + "breakout": [ + [ + "field", + { + "base-type": "type/Text", + "lib/uuid": "f10c2471-906d-4e8e-951e-704481053e96", + "effective-type": "type/Text" + }, + 1619 + ] + ], + "limit": 3 + } + ] + }, + "dimension_mappings": null, + "id": 129, + "legacy_query": null, + "parameter_mappings": [], + "display": "bar", + "archived_directly": false, + "entity_id": "1A_KOysA3SOXKBKP9W9u2", + "collection_preview": true, + "visualization_settings": { + "graph.dimensions": [ + "motivo" + ], + "graph.show_values": true, + "graph.label_value_formatting": "compact", + "graph.x_axis.labels_enabled": false, + "column_settings": { + "[\"name\",\"valor\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + }, + "graph.metrics": [ + "valor" + ] + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.547811Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 6, + "id": 116, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 129, + "target": [ + "dimension", + [ + "field", + 1617, + null + ] + ] + } + ], + "card_id": 129, + "entity_id": "zqEcvYVym7H3XriAp4lrb", + "visualization_settings": {}, + "size_y": 7, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 10 + }, + { + "size_x": 2, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 172, + "result_metadata": [ + { + "database_type": "TEXT", + "semantic_type": "type/Category", + "table_id": 172, + "lib/deduplicated-name": "orgao", + "lib/original-name": "orgao", + "lib/breakout?": true, + "name": "orgao", + "lib/source": "source/table-defaults", + "lib/source-column-alias": "orgao", + "lib/original-display-name": "Orgao", + "source": "breakout", + "field_ref": [ + "field", + 1618, + null + ], + "lib/transformation-added-base-type": true, + "effective_type": "type/Text", + "active": true, + "id": 1618, + "lib/desired-column-alias": "orgao", + "position": 1, + "visibility_type": "normal", + "display_name": "Orgao", + "fingerprint": { + "global": { + "distinct-count": 6, + "nil%": 0.0 + }, + "type": { + "type/Text": { + "percent-json": 0.0, + "percent-url": 0.0, + "percent-email": 0.0, + "percent-state": 0.0, + "average-length": 4.833333333333333 + } + } + }, + "base_type": "type/Text" + }, + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "valor", + "lib/original-name": "valor", + "name": "valor", + "lib/source": "source/aggregations", + "lib/source-column-alias": "valor", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "valor", + "display_name": "valor", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Top 3 por órgão", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.685702Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "order-by": [ + [ + "desc", + { + "lib/uuid": "4038f227-ba34-4045-b994-f7850fa64b80" + }, + [ + "aggregation", + { + "lib/uuid": "15d37782-20f7-47b5-93e8-69a40e7f7a1e" + }, + "fefe303e-de68-4034-a596-ba7295c1d4f5" + ] + ] + ], + "lib/type": "mbql.stage/mbql", + "source-table": 172, + "aggregation": [ + [ + "sum", + { + "name": "valor", + "lib/uuid": "fefe303e-de68-4034-a596-ba7295c1d4f5", + "display-name": "valor" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "564e5a4d-6dd6-44fa-befc-c958dc374d21", + "effective-type": "type/Float" + }, + 1621 + ] + ] + ], + "breakout": [ + [ + "field", + { + "base-type": "type/Text", + "lib/uuid": "6acf80aa-8014-4eca-8b40-03ba9aa389c0", + "effective-type": "type/Text" + }, + 1618 + ] + ], + "limit": 3 + } + ] + }, + "dimension_mappings": null, + "id": 130, + "legacy_query": null, + "parameter_mappings": [], + "display": "bar", + "archived_directly": false, + "entity_id": "-S9qKF8u1TSLo8p9bRvIA", + "collection_preview": true, + "visualization_settings": { + "graph.dimensions": [ + "orgao" + ], + "graph.show_values": true, + "graph.label_value_formatting": "compact", + "graph.x_axis.labels_enabled": false, + "column_settings": { + "[\"name\",\"valor\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + }, + "graph.metrics": [ + "valor" + ] + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.685702Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 8, + "id": 117, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 130, + "target": [ + "dimension", + [ + "field", + 1617, + null + ] + ] + } + ], + "card_id": 130, + "entity_id": "VbV4bzfCkF81vTFY-MT9u", + "visualization_settings": {}, + "size_y": 7, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 10 + }, + { + "size_x": 2, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": 172, + "result_metadata": [ + { + "database_type": "DATE", + "semantic_type": "type/CreationDate", + "table_id": 172, + "lib/temporal-unit": "month", + "lib/deduplicated-name": "competencia", + "lib/original-name": "competencia", + "lib/breakout?": true, + "unit": "month", + "name": "competencia", + "lib/source": "source/table-defaults", + "lib/source-column-alias": "competencia", + "lib/original-display-name": "Competencia", + "source": "breakout", + "field_ref": [ + "field", + 1617, + { + "temporal-unit": "month" + } + ], + "lib/transformation-added-base-type": true, + "effective_type": "type/Date", + "active": true, + "id": 1617, + "lib/desired-column-alias": "competencia", + "position": 0, + "visibility_type": "normal", + "inherited_temporal_unit": "month", + "display_name": "Competencia: Month", + "fingerprint": { + "global": { + "distinct-count": 6, + "nil%": 0.0 + }, + "type": { + "type/DateTime": { + "earliest": "2025-07-01", + "latest": "2025-12-01" + } + } + }, + "base_type": "type/Date" + }, + { + "semantic_type": "type/Currency", + "lib/deduplicated-name": "valor", + "lib/original-name": "valor", + "name": "valor", + "lib/source": "source/aggregations", + "lib/source-column-alias": "valor", + "source": "aggregation", + "field_ref": [ + "aggregation", + 0 + ], + "effective_type": "type/Float", + "lib/desired-column-alias": "valor", + "display_name": "valor", + "base_type": "type/Float" + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "query", + "download_perms": "full", + "name": "Top 3 por mês", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:23.844399Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "order-by": [ + [ + "desc", + { + "lib/uuid": "3883aaa0-170a-42de-b26b-4b6af320a0c4" + }, + [ + "aggregation", + { + "lib/uuid": "a331bc43-924c-4a84-bc9d-d6a07d595bf6" + }, + "4f3b282e-9b5b-4304-be7e-52991c99d18d" + ] + ] + ], + "lib/type": "mbql.stage/mbql", + "source-table": 172, + "aggregation": [ + [ + "sum", + { + "name": "valor", + "lib/uuid": "4f3b282e-9b5b-4304-be7e-52991c99d18d", + "display-name": "valor" + }, + [ + "field", + { + "base-type": "type/Float", + "lib/uuid": "388e9c8d-62fe-434a-80a4-a38ed2756327", + "effective-type": "type/Float" + }, + 1621 + ] + ] + ], + "breakout": [ + [ + "field", + { + "base-type": "type/Date", + "temporal-unit": "month", + "lib/uuid": "d74baf92-e339-4c90-85e8-059934db7922", + "effective-type": "type/Date" + }, + 1617 + ] + ], + "limit": 3 + } + ] + }, + "dimension_mappings": null, + "id": 131, + "legacy_query": null, + "parameter_mappings": [], + "display": "bar", + "archived_directly": false, + "entity_id": "L7pq4n5c7-MGZ5E_YcKd5", + "collection_preview": true, + "visualization_settings": { + "graph.dimensions": [ + "competencia" + ], + "graph.show_values": true, + "graph.label_value_formatting": "compact", + "graph.x_axis.labels_enabled": false, + "column_settings": { + "[\"name\",\"valor\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + }, + "graph.metrics": [ + "valor" + ] + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:23.844399Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 10, + "id": 118, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 131, + "target": [ + "dimension", + [ + "field", + 1617, + null + ] + ] + } + ], + "card_id": 131, + "entity_id": "CKMmxawVZVw_aRir1ujup", + "visualization_settings": {}, + "size_y": 7, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 10 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 5, + "collection_position": null, + "source_card_id": null, + "table_id": null, + "result_metadata": [ + { + "name": "orgao", + "database_type": "TEXT", + "base_type": "type/Text", + "display_name": "orgao", + "field_ref": [ + "field", + "orgao", + { + "base-type": "type/Text" + } + ] + }, + { + "name": "divergencia_financeira_bruta", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "divergencia_financeira_bruta", + "field_ref": [ + "field", + "divergencia_financeira_bruta", + { + "base-type": "type/Float" + } + ] + }, + { + "name": "divergencia_normativa", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "divergencia_normativa", + "field_ref": [ + "field", + "divergencia_normativa", + { + "base-type": "type/Float" + } + ] + }, + { + "name": "pessoas", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "pessoas", + "field_ref": [ + "field", + "pessoas", + { + "base-type": "type/Float" + } + ] + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "native", + "download_perms": "full", + "name": "Análise / detalhamento divergências", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:24.036299Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "template-tags": { + "periodo": { + "dimension": [ + "field", + { + "base-type": "type/Date", + "lib/uuid": "1e328897-87db-46fc-a06c-ea5391666b1d", + "effective-type": "type/Date" + }, + 1617 + ], + "id": "c0ffee01", + "name": "periodo", + "display-name": "Período", + "type": "dimension", + "widget-type": "date/all-options" + } + }, + "lib/type": "mbql.stage/native", + "native": "SELECT\n orgao,\n SUM(valor) AS divergencia_financeira_bruta,\n AVG(score_normativa) AS divergencia_normativa,\n SUM(qtd_pessoas) AS pessoas\nFROM abono_divergencia\nWHERE {{periodo}}\nGROUP BY orgao" + } + ] + }, + "dimension_mappings": null, + "id": 132, + "legacy_query": null, + "parameter_mappings": [], + "display": "scatter", + "archived_directly": false, + "entity_id": "9J5gkPgGm5-xamdiAHzQf", + "collection_preview": true, + "visualization_settings": { + "graph.dimensions": [ + "divergencia_financeira_bruta" + ], + "scatter.bubble": "pessoas", + "graph.x_axis.title_text": "Divergência financeira bruta", + "graph.y_axis.title_text": "Divergência normativa", + "click_behavior": { + "type": "crossfilter", + "parameterMapping": { + "b2c3d4e5": { + "id": "b2c3d4e5", + "source": { + "type": "column", + "id": "orgao", + "name": "orgao" + }, + "target": { + "type": "parameter", + "id": "b2c3d4e5" + } + } + } + }, + "column_settings": { + "[\"name\",\"divergencia_financeira_bruta\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + }, + "[\"name\",\"divergencia_normativa\"]": { + "decimals": 1 + } + }, + "graph.metrics": [ + "divergencia_normativa" + ] + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:24.036299Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 12, + "id": 119, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 132, + "target": [ + "dimension", + [ + "template-tag", + "periodo" + ] + ] + } + ], + "card_id": 132, + "entity_id": "fPDcFAQrd5v7vb0IBLRzS", + "visualization_settings": { + "graph.dimensions": [ + "divergencia_financeira_bruta" + ], + "scatter.bubble": "pessoas", + "graph.x_axis.title_text": "Divergência financeira bruta", + "graph.y_axis.title_text": "Divergência normativa", + "click_behavior": { + "type": "crossfilter", + "parameterMapping": { + "b2c3d4e5": { + "id": "b2c3d4e5", + "source": { + "type": "column", + "id": "orgao", + "name": "orgao" + }, + "target": { + "type": "parameter", + "id": "b2c3d4e5" + } + } + } + }, + "column_settings": { + "[\"name\",\"divergencia_financeira_bruta\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + }, + "[\"name\",\"divergencia_normativa\"]": { + "decimals": 1 + } + }, + "graph.metrics": [ + "divergencia_normativa" + ] + }, + "size_y": 14, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 4 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 15, + "collection_position": null, + "source_card_id": null, + "table_id": null, + "result_metadata": [ + { + "name": "motivo", + "database_type": "TEXT", + "base_type": "type/Text", + "display_name": "motivo", + "field_ref": [ + "field", + "motivo", + { + "base-type": "type/Text" + } + ] + }, + { + "name": "valor", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "valor", + "field_ref": [ + "field", + "valor", + { + "base-type": "type/Float" + } + ] + }, + { + "name": "percentual", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "percentual", + "semantic_type": "type/Share", + "field_ref": [ + "field", + "percentual", + { + "base-type": "type/Float" + } + ] + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "native", + "download_perms": "full", + "name": "Divergência financeira por motivo", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:24.179014Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "template-tags": { + "periodo": { + "dimension": [ + "field", + { + "base-type": "type/Date", + "lib/uuid": "fb83508f-19c9-482b-b4bb-51f267fd1d68", + "effective-type": "type/Date" + }, + 1617 + ], + "id": "c0ffee01", + "name": "periodo", + "display-name": "Período", + "type": "dimension", + "widget-type": "date/all-options" + }, + "orgao": { + "dimension": [ + "field", + { + "base-type": "type/Text", + "lib/uuid": "13da3a6d-80dc-43e5-b9f9-087a14616535", + "effective-type": "type/Text" + }, + 1618 + ], + "id": "c0ffee02", + "name": "orgao", + "display-name": "Órgão", + "type": "dimension", + "widget-type": "string/=" + } + }, + "lib/type": "mbql.stage/native", + "native": "SELECT\n motivo,\n SUM(valor) AS valor,\n ROUND(100.0 * SUM(valor) / SUM(SUM(valor)) OVER (), 1) AS percentual\nFROM abono_divergencia\nWHERE {{periodo}} AND {{orgao}}\nGROUP BY motivo\nORDER BY valor DESC" + } + ] + }, + "dimension_mappings": null, + "id": 133, + "legacy_query": null, + "parameter_mappings": [], + "display": "table", + "archived_directly": false, + "entity_id": "XfLFudnvzqfNC98dKk_Wa", + "collection_preview": true, + "visualization_settings": { + "table.pivot": false, + "column_settings": { + "[\"name\",\"valor\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + }, + "[\"name\",\"percentual\"]": { + "suffix": " %", + "decimals": 1 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:24.179014Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 18, + "id": 120, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 133, + "target": [ + "dimension", + [ + "template-tag", + "periodo" + ] + ] + }, + { + "parameter_id": "b2c3d4e5", + "card_id": 133, + "target": [ + "dimension", + [ + "template-tag", + "orgao" + ] + ] + } + ], + "card_id": 133, + "entity_id": "hed_Asf0vATq89LmlRAEf", + "visualization_settings": {}, + "size_y": 7, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 4 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 15, + "collection_position": null, + "source_card_id": null, + "table_id": null, + "result_metadata": [ + { + "name": "cargo", + "database_type": "TEXT", + "base_type": "type/Text", + "display_name": "cargo", + "field_ref": [ + "field", + "cargo", + { + "base-type": "type/Text" + } + ] + }, + { + "name": "pessoas", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "pessoas", + "field_ref": [ + "field", + "pessoas", + { + "base-type": "type/Float" + } + ] + }, + { + "name": "percentual", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "percentual", + "semantic_type": "type/Share", + "field_ref": [ + "field", + "percentual", + { + "base-type": "type/Float" + } + ] + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "native", + "download_perms": "full", + "name": "Pessoal afetado", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:24.312324Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "template-tags": { + "periodo": { + "dimension": [ + "field", + { + "base-type": "type/Date", + "lib/uuid": "94a57599-7cee-4821-96f5-142c4e92c0d6", + "effective-type": "type/Date" + }, + 1617 + ], + "id": "c0ffee01", + "name": "periodo", + "display-name": "Período", + "type": "dimension", + "widget-type": "date/all-options" + }, + "orgao": { + "dimension": [ + "field", + { + "base-type": "type/Text", + "lib/uuid": "106503ac-7707-458d-92a8-947378dd48d3", + "effective-type": "type/Text" + }, + 1618 + ], + "id": "c0ffee02", + "name": "orgao", + "display-name": "Órgão", + "type": "dimension", + "widget-type": "string/=" + } + }, + "lib/type": "mbql.stage/native", + "native": "SELECT\n cargo,\n SUM(qtd_pessoas) AS pessoas,\n ROUND(100.0 * SUM(qtd_pessoas) / SUM(SUM(qtd_pessoas)) OVER (), 1) AS percentual\nFROM abono_divergencia\nWHERE {{periodo}} AND {{orgao}}\nGROUP BY cargo\nORDER BY pessoas DESC" + } + ] + }, + "dimension_mappings": null, + "id": 134, + "legacy_query": null, + "parameter_mappings": [], + "display": "table", + "archived_directly": false, + "entity_id": "o8Y0mmKpi3Vx_gkN2mJws", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"percentual\"]": { + "suffix": " %", + "decimals": 1 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:24.312324Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 18, + "id": 121, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 134, + "target": [ + "dimension", + [ + "template-tag", + "periodo" + ] + ] + }, + { + "parameter_id": "b2c3d4e5", + "card_id": 134, + "target": [ + "dimension", + [ + "template-tag", + "orgao" + ] + ] + } + ], + "card_id": 134, + "entity_id": "qdJR1Tn78xPwtPpYDtvag", + "visualization_settings": {}, + "size_y": 5, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 11 + }, + { + "size_x": 6, + "dashboard_tab_id": null, + "series": [], + "inline_parameters": [], + "action_id": null, + "collection_authority_level": null, + "card": { + "cache_invalidated_at": null, + "description": null, + "archived": false, + "view_count": 15, + "collection_position": null, + "source_card_id": null, + "table_id": null, + "result_metadata": [ + { + "name": "media_mensal", + "database_type": "NUMERIC", + "base_type": "type/Float", + "display_name": "media_mensal", + "field_ref": [ + "field", + "media_mensal", + { + "base-type": "type/Float" + } + ] + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "card_schema": 23, + "database_id": 2, + "enable_embedding": false, + "collection_id": 7, + "query_type": "native", + "download_perms": "full", + "name": "Divergência média mensal", + "document_id": null, + "last_used_at": "2026-08-21T12:37:47.363586Z", + "type": "question", + "dimensions": null, + "query_average_duration": null, + "creator_id": 1, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:24.44477Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "dataset_query": { + "lib/type": "mbql/query", + "database": 2, + "stages": [ + { + "template-tags": { + "periodo": { + "dimension": [ + "field", + { + "base-type": "type/Date", + "lib/uuid": "665da6b5-015b-42cd-8e73-181a5793ce3a", + "effective-type": "type/Date" + }, + 1617 + ], + "id": "c0ffee01", + "name": "periodo", + "display-name": "Período", + "type": "dimension", + "widget-type": "date/all-options" + }, + "orgao": { + "dimension": [ + "field", + { + "base-type": "type/Text", + "lib/uuid": "edad5dd5-7a33-457e-b363-ec0b3ef5fc83", + "effective-type": "type/Text" + }, + 1618 + ], + "id": "c0ffee02", + "name": "orgao", + "display-name": "Órgão", + "type": "dimension", + "widget-type": "string/=" + } + }, + "lib/type": "mbql.stage/native", + "native": "SELECT ROUND(SUM(valor) * 1.0 / COUNT(DISTINCT competencia), 2) AS media_mensal\nFROM abono_divergencia\nWHERE {{periodo}} AND {{orgao}}" + } + ] + }, + "dimension_mappings": null, + "id": 135, + "legacy_query": null, + "parameter_mappings": [], + "display": "scalar", + "archived_directly": false, + "entity_id": "VgghGwXlbEGA1v6GcaKsS", + "collection_preview": true, + "visualization_settings": { + "column_settings": { + "[\"name\",\"media_mensal\"]": { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2 + } + } + }, + "metabase_version": "vLOCAL_DEV (06d1ba2ae111e66253209c01c244d6379acfc6dcb1911fa9ab6012cec9ce52e5)", + "parameters": [], + "dashboard_id": null, + "created_at": "2026-08-17T12:33:24.44477Z", + "public_uuid": null + }, + "updated_at": "2026-08-17T12:36:40.333137Z", + "col": 18, + "id": 122, + "parameter_mappings": [ + { + "parameter_id": "a1b2c3d4", + "card_id": 135, + "target": [ + "dimension", + [ + "template-tag", + "periodo" + ] + ] + }, + { + "parameter_id": "b2c3d4e5", + "card_id": 135, + "target": [ + "dimension", + [ + "template-tag", + "orgao" + ] + ] + } + ], + "card_id": 135, + "entity_id": "O4IWtbIHEOAjquMUrE_yt", + "visualization_settings": {}, + "size_y": 2, + "dashboard_id": 10, + "created_at": "2026-08-17T12:33:25.109933Z", + "row": 16 + } + ], + "embedding_type": null, + "initially_published_at": null, + "can_write": true, + "can_set_cache_policy": true, + "tabs": [], + "enable_embedding": false, + "collection_id": 7, + "show_in_getting_started": false, + "name": "Abono de Permanência", + "is_remote_synced": false, + "width": "full", + "caveats": null, + "collection_authority_level": null, + "creator_id": 1, + "can_restore": false, + "moderation_reviews": [], + "updated_at": "2026-08-17T12:33:24.669349Z", + "made_public_by_id": null, + "embedding_params": null, + "cache_ttl": null, + "last_used_param_values": { + "a1b2c3d4": "2025-07-01~2025-12-31", + "b2c3d4e5": [ + "SEFAZ" + ] + }, + "id": 10, + "last_viewed_at": "2026-08-21T12:37:47.087805Z", + "position": null, + "archived_directly": false, + "entity_id": "TYFfGoyCbmJkLJqeZa90y", + "param_fields": { + "a1b2c3d4": [ + { + "semantic_type": "type/CreationDate", + "table_id": 172, + "name": "competencia", + "has_field_values": "none", + "fk_target_field_id": null, + "dimensions": [], + "id": 1617, + "target": null, + "display_name": "Competencia", + "name_field": null, + "base_type": "type/Date" + }, + { + "semantic_type": "type/CreationDate", + "table_id": 171, + "name": "competencia", + "has_field_values": "none", + "fk_target_field_id": null, + "dimensions": [], + "id": 1610, + "target": null, + "display_name": "Competencia", + "name_field": null, + "base_type": "type/Date" + } + ], + "b2c3d4e5": [ + { + "semantic_type": "type/Category", + "table_id": 172, + "name": "orgao", + "has_field_values": "list", + "fk_target_field_id": null, + "dimensions": [], + "id": 1618, + "target": null, + "display_name": "Orgao", + "name_field": null, + "base_type": "type/Text" + } + ], + "c0ffee01": [ + { + "semantic_type": "type/CreationDate", + "table_id": 172, + "name": "competencia", + "has_field_values": "none", + "fk_target_field_id": null, + "dimensions": [], + "id": 1617, + "target": null, + "display_name": "Competencia", + "name_field": null, + "base_type": "type/Date" + } + ], + "c0ffee02": [ + { + "semantic_type": "type/Category", + "table_id": 172, + "name": "orgao", + "has_field_values": "list", + "fk_target_field_id": null, + "dimensions": [], + "id": 1618, + "target": null, + "display_name": "Orgao", + "name_field": null, + "base_type": "type/Text" + } + ] + }, + "last-edit-info": { + "id": 1, + "email": "oxymus@gmail.com", + "first_name": "Felipe", + "last_name": "Zerede", + "timestamp": "2026-08-17T12:36:40.423306Z" + }, + "collection": { + "authority_level": null, + "description": "Dashboard nativo de demonstração — dados mock, sem integração com a base do cliente.", + "archived": false, + "workspace_id": null, + "slug": "abono_de_permanencia__demo_", + "archive_operation_id": null, + "name": "Abono de Permanência (demo)", + "is_remote_synced": false, + "personal_owner_id": null, + "type": null, + "is_sample": false, + "effective_location": "/", + "id": 7, + "archived_directly": null, + "entity_id": "P7Af9XvTLKPPl3Taf5f2g", + "location": "/", + "namespace": null, + "is_personal": false, + "created_at": "2026-08-17T12:33:21.419024Z" + }, + "parameters": [ + { + "id": "a1b2c3d4", + "name": "Período", + "slug": "periodo", + "type": "date/all-options", + "sectionId": "date", + "default": "2025-07-01~2025-12-31" + }, + { + "id": "b2c3d4e5", + "name": "Órgão", + "slug": "orgao", + "type": "string/=", + "sectionId": "string", + "default": "Seduc", + "isMultiSelect": false + } + ], + "auto_apply_filters": true, + "created_at": "2026-08-17T12:33:24.544229Z", + "public_uuid": null, + "points_of_interest": null, + "can_delete": false +} \ No newline at end of file diff --git a/docs/internal/abono-demo/seed_abono_demo.py b/docs/internal/abono-demo/seed_abono_demo.py new file mode 100644 index 0000000000..3421acc403 --- /dev/null +++ b/docs/internal/abono-demo/seed_abono_demo.py @@ -0,0 +1,824 @@ +#!/usr/bin/env python3 +"""Seed a native Metabase dashboard for the Abono de Permanência mock. + +Requires a running Metabase (admin user) and the SQLite mock built next to this file. + + python3 docs/internal/abono-demo/seed_abono_demo.py + +Environment: + MB_URL default http://localhost:3000 + MB_EMAIL admin email + MB_PASSWORD admin password +""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +import time +import urllib.error +import urllib.request +from pathlib import Path +from typing import Any + +HERE = Path(__file__).resolve().parent +if str(HERE) not in sys.path: + sys.path.insert(0, str(HERE)) + +from build_sqlite import build # noqa: E402 + +COLLECTION_NAME = "Abono de Permanência (demo)" +DATABASE_NAME = "Abono de Permanência (mock)" +DASHBOARD_NAME = "Abono de Permanência" + +PARAM_PERIODO = "a1b2c3d4" +PARAM_ORGAO = "b2c3d4e5" + +DEFAULT_PERIODO = "2025-07-01~2025-12-31" +DEFAULT_ORGAO = "Seduc" + +BRL = { + "number_style": "currency", + "currency": "BRL", + "currency_style": "symbol", + "decimals": 2, +} + + +class MetabaseClient: + def __init__(self, base_url: str, email: str, password: str): + self.base_url = base_url.rstrip("/") + self.session_id = self._login(email, password) + + def _login(self, email: str, password: str) -> str: + data = self.request( + "POST", + "/api/session", + {"username": email, "password": password}, + authenticated=False, + ) + session_id = data.get("id") + if not session_id: + raise RuntimeError(f"Login não retornou sessão: {data}") + return session_id + + def request( + self, + method: str, + path: str, + body: Any | None = None, + authenticated: bool = True, + ) -> Any: + url = self.base_url + path + headers = {"Content-Type": "application/json"} + if authenticated: + headers["X-Metabase-Session"] = self.session_id + payload = None if body is None else json.dumps(body).encode("utf-8") + req = urllib.request.Request(url, data=payload, headers=headers, method=method) + try: + with urllib.request.urlopen(req, timeout=120) as resp: + raw = resp.read() + if not raw: + return None + return json.loads(raw.decode("utf-8")) + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"{method} {path} -> HTTP {exc.code}: {detail}") from exc + except urllib.error.URLError as exc: + raise RuntimeError( + f"Não conectou em {self.base_url} ({exc.reason}). " + "Suba o Metabase e tente de novo." + ) from exc + + +def col_settings(*pairs: tuple[str, dict]) -> dict: + return {json.dumps(["name", name]): settings for name, settings in pairs} + + +def field_ref(field_id: int, extra: dict | None = None) -> list: + return ["field", field_id, extra] + + +def mbql(database_id: int, query: dict) -> dict: + return {"database": database_id, "type": "query", "query": query} + + +def named_sum(field_id: int, name: str) -> list: + return [ + "aggregation-options", + ["sum", field_ref(field_id)], + {"name": name, "display-name": name}, + ] + + +def named_avg(field_id: int, name: str) -> list: + return [ + "aggregation-options", + ["avg", field_ref(field_id)], + {"name": name, "display-name": name}, + ] + + +def map_dimension(parameter_id: str, card_id: int, field_id: int, extra: dict | None = None) -> dict: + return { + "parameter_id": parameter_id, + "card_id": card_id, + "target": ["dimension", field_ref(field_id, extra)], + } + + +def map_template_tag(parameter_id: str, card_id: int, tag: str) -> dict: + return { + "parameter_id": parameter_id, + "card_id": card_id, + "target": ["dimension", ["template-tag", tag]], + } + + +def date_tag(field_id: int) -> dict: + return { + "id": "c0ffee01", + "name": "periodo", + "display-name": "Período", + "type": "dimension", + "dimension": field_ref(field_id), + "widget-type": "date/all-options", + } + + +def orgao_tag(field_id: int) -> dict: + return { + "id": "c0ffee02", + "name": "orgao", + "display-name": "Órgão", + "type": "dimension", + "dimension": field_ref(field_id), + "widget-type": "string/=", + } + + +def virtual_heading(text: str) -> dict: + card = { + "name": None, + "display": "heading", + "visualization_settings": {}, + "archived": False, + } + return { + "text": text, + "virtual_card": card, + "dashcard.background": False, + } + + +def find_named(items: list[dict], name: str) -> dict | None: + wanted = name.lower() + for item in items: + if (item.get("name") or "").lower() == wanted: + return item + return None + + +def ensure_database(mb: MetabaseClient, sqlite_file: Path) -> dict: + dbs = mb.request("GET", "/api/database") + data = dbs.get("data", dbs) if isinstance(dbs, dict) else dbs + existing = find_named(data, DATABASE_NAME) + details = {"db": str(sqlite_file.resolve())} + if existing: + mb.request( + "PUT", + f"/api/database/{existing['id']}", + {"name": DATABASE_NAME, "engine": "sqlite", "details": details, "is_full_sync": True}, + ) + db_id = existing["id"] + else: + created = mb.request( + "POST", + "/api/database", + { + "name": DATABASE_NAME, + "engine": "sqlite", + "details": details, + "is_full_sync": True, + "is_on_demand": False, + }, + ) + db_id = created["id"] + mb.request("POST", f"/api/database/{db_id}/sync_schema") + return wait_for_tables(mb, db_id) + + +def wait_for_tables(mb: MetabaseClient, db_id: int, timeout: int = 90) -> dict: + deadline = time.time() + timeout + while time.time() < deadline: + meta = mb.request("GET", f"/api/database/{db_id}/metadata") + tables = {t["name"]: t for t in meta.get("tables", []) if t.get("active", True)} + if "abono_folha" in tables and "abono_divergencia" in tables: + if all(t.get("fields") for t in (tables["abono_folha"], tables["abono_divergencia"])): + return tables + time.sleep(2) + raise RuntimeError("Timeout esperando o sync das tabelas mock. Veja Admin > Databases.") + + +def fields_by_name(table: dict) -> dict[str, dict]: + return {f["name"]: f for f in table.get("fields", [])} + + +def classify_fields(mb: MetabaseClient, tables: dict) -> None: + folha = fields_by_name(tables["abono_folha"]) + div = fields_by_name(tables["abono_divergencia"]) + updates = [ + (folha["competencia"]["id"], {"semantic_type": "type/CreationDate"}), + (folha["valor_total"]["id"], {"semantic_type": "type/Currency"}), + (folha["valor_analisado"]["id"], {"semantic_type": "type/Currency"}), + (folha["valor_divergencias"]["id"], {"semantic_type": "type/Currency"}), + (div["competencia"]["id"], {"semantic_type": "type/CreationDate"}), + (div["orgao"]["id"], {"semantic_type": "type/Category", "has_field_values": "list"}), + (div["motivo"]["id"], {"semantic_type": "type/Category", "has_field_values": "list"}), + (div["cargo"]["id"], {"semantic_type": "type/Category", "has_field_values": "list"}), + (div["valor"]["id"], {"semantic_type": "type/Currency"}), + ] + for field_id, body in updates: + mb.request("PUT", f"/api/field/{field_id}", body) + mb.request("POST", f"/api/field/{div['orgao']['id']}/rescan_values") + mb.request("POST", f"/api/field/{div['motivo']['id']}/rescan_values") + + +def ensure_collection(mb: MetabaseClient) -> int: + root = mb.request("GET", "/api/collection/root/items?models=collection") + items = root.get("data", []) + existing = find_named(items, COLLECTION_NAME) + if existing: + return existing["id"] + created = mb.request( + "POST", + "/api/collection", + { + "name": COLLECTION_NAME, + "description": "Dashboard nativo de demonstração — dados mock, sem integração com a base do cliente.", + }, + ) + return created["id"] + + +def archive_old_dashboard(mb: MetabaseClient, collection_id: int) -> None: + items = mb.request( + "GET", + f"/api/collection/{collection_id}/items?models=dashboard", + ).get("data", []) + for item in items: + if item.get("name") == DASHBOARD_NAME: + mb.request("PUT", f"/api/dashboard/{item['id']}", {"archived": True}) + + +def create_card(mb: MetabaseClient, collection_id: int, **card: Any) -> dict: + payload = { + "collection_id": collection_id, + "type": "question", + "visualization_settings": {}, + **card, + } + return mb.request("POST", "/api/card", payload) + + +def native_query(database_id: int, sql: str, tags: dict) -> dict: + return { + "database": database_id, + "type": "native", + "native": {"query": sql, "template-tags": tags}, + } + + +def seed(mb: MetabaseClient, sqlite_file: Path) -> str: + tables = ensure_database(mb, sqlite_file) + classify_fields(mb, tables) + collection_id = ensure_collection(mb) + archive_old_dashboard(mb, collection_id) + + folha = tables["abono_folha"] + diverg = tables["abono_divergencia"] + ff = fields_by_name(folha) + df = fields_by_name(diverg) + db_id = folha["db_id"] + + folha_id = folha["id"] + diverg_id = diverg["id"] + f_comp = ff["competencia"]["id"] + f_valor = ff["valor_total"]["id"] + f_bens = ff["qtd_beneficiarios"]["id"] + f_ok_v = ff["valor_analisado"]["id"] + f_ok_p = ff["qtd_analisado"]["id"] + f_div_v = ff["valor_divergencias"]["id"] + f_div_p = ff["qtd_divergencias"]["id"] + d_comp = df["competencia"]["id"] + d_orgao = df["orgao"]["id"] + d_motivo = df["motivo"]["id"] + d_cargo = df["cargo"]["id"] + d_valor = df["valor"]["id"] + d_pessoas = df["qtd_pessoas"]["id"] + d_score = df["score_normativa"]["id"] + + tags_periodo = {"periodo": date_tag(d_comp)} + tags_periodo_folha = {"periodo": date_tag(f_comp)} + tags_both = {"periodo": date_tag(d_comp), "orgao": orgao_tag(d_orgao)} + + red_segment = [{"min": 0, "max": None, "color": "#E75C58", "label": "Divergência"}] + + cards: dict[str, dict] = {} + + def scalar(name: str, field_id: int, table_id: int, extra_settings: dict | None = None) -> dict: + settings = { + "column_settings": col_settings(("sum", BRL)), + **(extra_settings or {}), + } + return create_card( + mb, + collection_id, + name=name, + display="scalar", + dataset_query=mbql( + db_id, + {"source-table": table_id, "aggregation": [["sum", field_ref(field_id)]]}, + ), + visualization_settings=settings, + ) + + cards["valor_total"] = scalar("Valor total", f_valor, folha_id) + cards["valor_medio"] = create_card( + mb, + collection_id, + name="Valor mensal médio", + display="scalar", + dataset_query=mbql( + db_id, + {"source-table": folha_id, "aggregation": [named_avg(f_valor, "media")]}, + ), + visualization_settings={"column_settings": col_settings(("media", BRL))}, + ) + cards["valor_benef"] = create_card( + mb, + collection_id, + name="Valor por beneficiário/mês", + display="scalar", + dataset_query=mbql( + db_id, + { + "source-table": folha_id, + "aggregation": [ + [ + "aggregation-options", + ["/", ["sum", field_ref(f_valor)], ["sum", field_ref(f_bens)]], + {"name": "per_capita", "display-name": "Valor por beneficiário/mês"}, + ] + ], + }, + ), + visualization_settings={"column_settings": col_settings(("per_capita", BRL))}, + ) + + cards["evolucao"] = create_card( + mb, + collection_id, + name="Evolução", + display="combo", + dataset_query=mbql( + db_id, + { + "source-table": folha_id, + "aggregation": [ + named_sum(f_valor, "Valor total"), + named_sum(f_bens, "Beneficiários"), + ], + "breakout": [field_ref(f_comp, {"temporal-unit": "month"})], + }, + ), + visualization_settings={ + "graph.dimensions": ["competencia"], + "graph.metrics": ["Valor total", "Beneficiários"], + "graph.split_panels": True, + "graph.show_values": True, + "graph.label_value_frequency": "all", + "graph.label_value_formatting": "compact", + "graph.x_axis.labels_enabled": False, + "series_settings": { + "Valor total": {"display": "line", "color": "#509EE3", "line.marker_enabled": True}, + "Beneficiários": {"display": "bar", "color": "#7BBCE7"}, + }, + "column_settings": col_settings(("Valor total", {**BRL, "decimals": 1})), + }, + ) + + cards["analisado_valor"] = scalar("Analisado — valor", f_ok_v, folha_id) + cards["diverg_valor"] = scalar( + "Divergências — valor", + f_div_v, + folha_id, + extra_settings={"scalar.segments": red_segment}, + ) + cards["analisado_pessoas"] = create_card( + mb, + collection_id, + name="Analisado — pessoas", + display="scalar", + dataset_query=mbql( + db_id, + {"source-table": folha_id, "aggregation": [named_sum(f_ok_p, "pessoas")]}, + ), + visualization_settings={}, + ) + cards["diverg_pessoas"] = create_card( + mb, + collection_id, + name="Divergências — pessoas", + display="scalar", + dataset_query=mbql( + db_id, + {"source-table": folha_id, "aggregation": [named_sum(f_div_p, "pessoas")]}, + ), + visualization_settings={"scalar.segments": red_segment}, + ) + cards["analisado_pct"] = create_card( + mb, + collection_id, + name="Analisado — % do valor", + display="scalar", + dataset_query=mbql( + db_id, + { + "source-table": folha_id, + "aggregation": [ + [ + "aggregation-options", + ["/", ["sum", field_ref(f_ok_v)], ["sum", field_ref(f_valor)]], + {"name": "pct", "display-name": "% analisado"}, + ] + ], + }, + ), + visualization_settings={ + "column_settings": col_settings(("pct", {"number_style": "percent", "decimals": 1})), + }, + ) + cards["diverg_pct"] = create_card( + mb, + collection_id, + name="Divergências — % do valor", + display="scalar", + dataset_query=mbql( + db_id, + { + "source-table": folha_id, + "aggregation": [ + [ + "aggregation-options", + ["/", ["sum", field_ref(f_div_v)], ["sum", field_ref(f_valor)]], + {"name": "pct", "display-name": "% divergências"}, + ] + ], + }, + ), + visualization_settings={ + "scalar.segments": red_segment, + "column_settings": col_settings(("pct", {"number_style": "percent", "decimals": 1})), + }, + ) + + def top3_bar(name: str, breakout_field: int, extra: dict | None = None) -> dict: + return create_card( + mb, + collection_id, + name=name, + display="bar", + dataset_query=mbql( + db_id, + { + "source-table": diverg_id, + "aggregation": [named_sum(d_valor, "valor")], + "breakout": [field_ref(breakout_field, extra)], + "order-by": [["desc", ["aggregation", 0]]], + "limit": 3, + }, + ), + visualization_settings={ + "graph.dimensions": [ + "motivo" if breakout_field == d_motivo else "orgao" if breakout_field == d_orgao else "competencia" + ], + "graph.metrics": ["valor"], + "graph.show_values": True, + "graph.label_value_formatting": "compact", + "graph.x_axis.labels_enabled": False, + "column_settings": col_settings(("valor", BRL)), + }, + ) + + cards["top_motivo"] = top3_bar("Top 3 por motivo", d_motivo) + cards["top_orgao"] = top3_bar("Top 3 por órgão", d_orgao) + cards["top_mes"] = top3_bar("Top 3 por mês", d_comp, {"temporal-unit": "month"}) + + scatter_sql = """ +SELECT + orgao, + SUM(valor) AS divergencia_financeira_bruta, + AVG(score_normativa) AS divergencia_normativa, + SUM(qtd_pessoas) AS pessoas +FROM abono_divergencia +WHERE {{periodo}} +GROUP BY orgao +""".strip() + cards["scatter"] = create_card( + mb, + collection_id, + name="Análise / detalhamento divergências", + display="scatter", + dataset_query=native_query(db_id, scatter_sql, tags_periodo), + visualization_settings={ + "graph.dimensions": ["divergencia_financeira_bruta"], + "graph.metrics": ["divergencia_normativa"], + "scatter.bubble": "pessoas", + "graph.x_axis.title_text": "Divergência financeira bruta", + "graph.y_axis.title_text": "Divergência normativa", + "click_behavior": { + "type": "crossfilter", + "parameterMapping": { + PARAM_ORGAO: { + "id": PARAM_ORGAO, + "source": {"type": "column", "id": "orgao", "name": "orgao"}, + "target": {"type": "parameter", "id": PARAM_ORGAO}, + } + }, + }, + "column_settings": col_settings( + ("divergencia_financeira_bruta", BRL), + ("divergencia_normativa", {"decimals": 1}), + ), + }, + ) + + detalhe_sql = """ +SELECT + motivo, + SUM(valor) AS valor, + ROUND(100.0 * SUM(valor) / SUM(SUM(valor)) OVER (), 1) AS percentual +FROM abono_divergencia +WHERE {{periodo}} AND {{orgao}} +GROUP BY motivo +ORDER BY valor DESC +""".strip() + cards["detalhe_motivo"] = create_card( + mb, + collection_id, + name="Divergência financeira por motivo", + display="table", + dataset_query=native_query(db_id, detalhe_sql, tags_both), + visualization_settings={ + "table.pivot": False, + "column_settings": col_settings( + ("valor", BRL), + ("percentual", {"suffix": " %", "decimals": 1}), + ), + }, + ) + + cargos_sql = """ +SELECT + cargo, + SUM(qtd_pessoas) AS pessoas, + ROUND(100.0 * SUM(qtd_pessoas) / SUM(SUM(qtd_pessoas)) OVER (), 1) AS percentual +FROM abono_divergencia +WHERE {{periodo}} AND {{orgao}} +GROUP BY cargo +ORDER BY pessoas DESC +""".strip() + cards["detalhe_cargos"] = create_card( + mb, + collection_id, + name="Pessoal afetado", + display="table", + dataset_query=native_query(db_id, cargos_sql, tags_both), + visualization_settings={ + "column_settings": col_settings(("percentual", {"suffix": " %", "decimals": 1})), + }, + ) + + media_sql = """ +SELECT ROUND(SUM(valor) * 1.0 / COUNT(DISTINCT competencia), 2) AS media_mensal +FROM abono_divergencia +WHERE {{periodo}} AND {{orgao}} +""".strip() + cards["media_div"] = create_card( + mb, + collection_id, + name="Divergência média mensal", + display="scalar", + dataset_query=native_query(db_id, media_sql, tags_both), + visualization_settings={"column_settings": col_settings(("media_mensal", BRL))}, + ) + + dashboard = mb.request( + "POST", + "/api/dashboard", + { + "name": DASHBOARD_NAME, + "description": ( + "Protótipo nativo (nível A). Dados mock de jul–dez/2025. " + "Clique numa bolha para filtrar o detalhe do órgão." + ), + "collection_id": collection_id, + "parameters": [ + { + "id": PARAM_PERIODO, + "name": "Período", + "slug": "periodo", + "type": "date/all-options", + "sectionId": "date", + "default": DEFAULT_PERIODO, + }, + { + "id": PARAM_ORGAO, + "name": "Órgão", + "slug": "orgao", + "type": "string/=", + "sectionId": "string", + "default": DEFAULT_ORGAO, + "isMultiSelect": False, + }, + ], + }, + ) + dash_id = dashboard["id"] + mb.request("PUT", f"/api/dashboard/{dash_id}", {"width": "full"}) + + def cid(key: str) -> int: + return cards[key]["id"] + + def folha_maps(card_key: str) -> list[dict]: + return [map_dimension(PARAM_PERIODO, cid(card_key), f_comp)] + + def diverg_maps(card_key: str) -> list[dict]: + return [map_dimension(PARAM_PERIODO, cid(card_key), d_comp)] + + def native_periodo(card_key: str) -> list[dict]: + return [map_template_tag(PARAM_PERIODO, cid(card_key), "periodo")] + + def native_both(card_key: str) -> list[dict]: + return [ + map_template_tag(PARAM_PERIODO, cid(card_key), "periodo"), + map_template_tag(PARAM_ORGAO, cid(card_key), "orgao"), + ] + + heading_main = { + "id": -1, + "card_id": None, + "row": 0, + "col": 0, + "size_x": 24, + "size_y": 2, + "inline_parameters": [PARAM_PERIODO], + "parameter_mappings": [], + "visualization_settings": virtual_heading("Abono de Permanência"), + "series": [], + } + heading_analise = { + "id": -2, + "card_id": None, + "row": 1, + "col": 12, + "size_x": 6, + "size_y": 1, + "inline_parameters": [], + "parameter_mappings": [], + "visualization_settings": virtual_heading("Análise / detalhamento divergências"), + "series": [], + } + heading_orgao = { + "id": -3, + "card_id": None, + "row": 1, + "col": 18, + "size_x": 6, + "size_y": 1, + "inline_parameters": [PARAM_ORGAO], + "parameter_mappings": [ + { + "parameter_id": PARAM_ORGAO, + "target": ["text-tag", "orgao"], + } + ], + "visualization_settings": virtual_heading("{{orgao}}"), + "series": [], + } + heading_top3 = { + "id": -4, + "card_id": None, + "row": 8, + "col": 6, + "size_x": 6, + "size_y": 1, + "inline_parameters": [], + "parameter_mappings": [], + "visualization_settings": virtual_heading("Top 3 divergências"), + "series": [], + } + + question_cards = [ + # Coluna 1 + {"id": -10, "card_id": cid("valor_total"), "row": 2, "col": 0, "size_x": 6, "size_y": 2, "parameter_mappings": folha_maps("valor_total")}, + {"id": -11, "card_id": cid("valor_medio"), "row": 4, "col": 0, "size_x": 6, "size_y": 2, "parameter_mappings": folha_maps("valor_medio")}, + {"id": -12, "card_id": cid("valor_benef"), "row": 6, "col": 0, "size_x": 6, "size_y": 2, "parameter_mappings": folha_maps("valor_benef")}, + {"id": -13, "card_id": cid("evolucao"), "row": 8, "col": 0, "size_x": 6, "size_y": 8, "parameter_mappings": folha_maps("evolucao")}, + # Coluna 2 + {"id": -20, "card_id": cid("analisado_valor"), "row": 2, "col": 6, "size_x": 3, "size_y": 3, "parameter_mappings": folha_maps("analisado_valor")}, + {"id": -21, "card_id": cid("diverg_valor"), "row": 2, "col": 9, "size_x": 3, "size_y": 3, "parameter_mappings": folha_maps("diverg_valor")}, + {"id": -22, "card_id": cid("analisado_pessoas"), "row": 5, "col": 6, "size_x": 3, "size_y": 2, "parameter_mappings": folha_maps("analisado_pessoas")}, + {"id": -23, "card_id": cid("diverg_pessoas"), "row": 5, "col": 9, "size_x": 3, "size_y": 2, "parameter_mappings": folha_maps("diverg_pessoas")}, + {"id": -24, "card_id": cid("analisado_pct"), "row": 7, "col": 6, "size_x": 3, "size_y": 1, "parameter_mappings": folha_maps("analisado_pct")}, + {"id": -25, "card_id": cid("diverg_pct"), "row": 7, "col": 9, "size_x": 3, "size_y": 1, "parameter_mappings": folha_maps("diverg_pct")}, + {"id": -26, "card_id": cid("top_motivo"), "row": 9, "col": 6, "size_x": 2, "size_y": 7, "parameter_mappings": diverg_maps("top_motivo")}, + {"id": -27, "card_id": cid("top_orgao"), "row": 9, "col": 8, "size_x": 2, "size_y": 7, "parameter_mappings": diverg_maps("top_orgao")}, + {"id": -28, "card_id": cid("top_mes"), "row": 9, "col": 10, "size_x": 2, "size_y": 7, "parameter_mappings": diverg_maps("top_mes")}, + # Coluna 3 + {"id": -30, "card_id": cid("scatter"), "row": 2, "col": 12, "size_x": 6, "size_y": 14, "parameter_mappings": native_periodo("scatter"), "visualization_settings": cards["scatter"]["visualization_settings"]}, + # Coluna 4 + {"id": -40, "card_id": cid("detalhe_motivo"), "row": 2, "col": 18, "size_x": 6, "size_y": 7, "parameter_mappings": native_both("detalhe_motivo")}, + {"id": -41, "card_id": cid("detalhe_cargos"), "row": 9, "col": 18, "size_x": 6, "size_y": 5, "parameter_mappings": native_both("detalhe_cargos")}, + {"id": -42, "card_id": cid("media_div"), "row": 14, "col": 18, "size_x": 6, "size_y": 2, "parameter_mappings": native_both("media_div")}, + ] + for dc in question_cards: + dc.setdefault("series", []) + dc.setdefault("inline_parameters", []) + dc.setdefault("visualization_settings", {}) + + mb.request( + "PUT", + f"/api/dashboard/{dash_id}", + { + "name": DASHBOARD_NAME, + "width": "full", + "parameters": dashboard["parameters"] + if dashboard.get("parameters") + else [ + { + "id": PARAM_PERIODO, + "name": "Período", + "slug": "periodo", + "type": "date/all-options", + "sectionId": "date", + "default": DEFAULT_PERIODO, + }, + { + "id": PARAM_ORGAO, + "name": "Órgão", + "slug": "orgao", + "type": "string/=", + "sectionId": "string", + "default": DEFAULT_ORGAO, + "isMultiSelect": False, + }, + ], + "dashcards": [heading_main, heading_analise, heading_orgao, heading_top3, *question_cards], + }, + ) + + return f"{mb.base_url}/dashboard/{dash_id}" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--sqlite-only", + action="store_true", + help="Só gera o arquivo SQLite, sem chamar a API do Metabase.", + ) + parser.add_argument("--url", default=os.environ.get("MB_URL", "http://localhost:3000")) + parser.add_argument("--email", default=os.environ.get("MB_EMAIL", "")) + parser.add_argument("--password", default=os.environ.get("MB_PASSWORD", "")) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + sqlite_file = build() + print(f"SQLite mock: {sqlite_file}") + if args.sqlite_only: + return 0 + if not args.email or not args.password: + print( + "Informe MB_EMAIL e MB_PASSWORD (ou --email / --password).\n" + "O SQLite já foi gerado; rode o script de novo depois do login admin.", + file=sys.stderr, + ) + return 1 + user = MetabaseClient(args.url, args.email, args.password) + me = user.request("GET", "/api/user/current") + if not me.get("is_superuser"): + print("A conta precisa ser admin para adicionar o banco SQLite.", file=sys.stderr) + return 1 + url = seed(user, sqlite_file) + print(f"Dashboard criado: {url}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())