arcadiasuite/PLANO_BI_SUPERSET.md

30 KiB

PLANO BI — Substituição Metabase → Apache Superset

Análise Real + Plano de Migração

Versão 1.0 — Março 2026


1. DIAGNÓSTICO — ESTADO ATUAL

1.1 O que foi descoberto no código

O documento do Replit referenciava Metabase (Java, :8088) como o pilar visual do BI. Ao analisar o código real, a situação é diferente:

Componente Nome no doc Nome real no código Status
Plataforma BI Visual "Metabase" "MetaSet" (alias) Metabase por baixo
Proxy /metabase/* server/metabase/proxy.ts Funcional
Auth Metabase session server/metaset/routes.ts (autologin) Funcional
Client MetabaseClient server/metaset/client.ts Funcional
UI Advanced tab iframe /api/bi/metaset/autologin/metabase/ Funcional
Superset Não mencionado docker-compose.yml linha 165 JÁ CONFIGURADO

Descoberta crítica: O Apache Superset já está no docker-compose.yml:

superset:
  image: apache/superset:4.1.0
  restart: unless-stopped
  environment:
    SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
    DATABASE_URL: postgresql://arcadia:arcadia123@db:5432/arcadia_superset
  ports:
    - "8088:8088"
  depends_on:
    db:
      condition: service_healthy
  profiles: [bi]

O Superset está configurado, mas não integrado ao gateway Node.js. O que falta é a ponte.

1.2 Arquivos que precisam mudar vs o que fica igual

Mudam:

server/metabase/proxy.ts      → substituir por server/superset/proxy.ts
server/metaset/routes.ts      → substituir por server/superset/routes.ts
server/metaset/client.ts      → substituir por server/superset/client.ts
client/src/pages/BiWorkspace.tsx (Advanced tab apenas)
docker-compose.yml (Superset config + init)
docker-compose.prod.yml (adicionar Superset)
.env.example (novas variáveis)

NÃO mudam (ficam exatamente iguais):

server/python/bi_engine.py    ← Motor Python :8004 continua intacto
server/bi/routes.ts           ← CRUD de datasets/charts/dashboards
server/bi/upload.ts           ← ETL pipeline
server/bi/staging.ts          ← Staging
server/bi/engine-proxy.ts     ← Proxy para BI Engine Python
client/src/pages/BiWorkspace.tsx (7 de 8 tabs ficam iguais)
Todas as tabelas do banco (bi_datasets, bi_charts, etc.)

2. POR QUE SUPERSET É MELHOR PARA O CASO

2.1 Metabase vs Superset

Critério Metabase Apache Superset
Runtime Java (JVM, 500MB+ RAM) Python/Flask (mais leve)
Embedding Iframe com cookie de sessão Guest Token JWT (seguro, sem credenciais)
"Qualquer tela" Iframe estático, limitado @superset-ui/embedded-sdk — componente React nativo
REST API Limitada e não documentada REST API v1 completa e documentada
Consistência de stack Java (fora do padrão) Python (mesmo stack dos motores existentes)
Docker image metabase/metabase ~700MB apache/superset ~400MB
SQL Lab Básico Poderoso (autocomplete, histórico, salvar queries)
Alertas Básico Avançado (webhooks, Slack, email)
Tipos de chart ~20 50+ (incluindo mapas, sunburst, funnel)
Banco de metadados H2/Postgres separado Mesmo PostgreSQL da Arcádia

2.2 A feature que o usuário pediu: "funcionar em qualquer tela"

O Superset resolve isso com Guest Token + Embedded SDK:

Fluxo atual (Metabase):
  BiWorkspace → iframe → /metabase/ → autologin com cookie
  ❌ Só funciona na tab Advanced do BiWorkspace
  ❌ Cookie expira, precisa re-autenticar
  ❌ Não embeda em outras páginas facilmente

Fluxo novo (Superset):
  QUALQUER página React → <EmbeddedDashboard dashboardId="xxx" />
  ✅ Funciona no Dashboard principal (/)
  ✅ Funciona no SOE (/soe tab Financeiro)
  ✅ Funciona no /contabil
  ✅ Funciona no /financeiro
  ✅ Funciona em qualquer tela do Arcádia
  ✅ Guest Token renovado automaticamente (sem re-login)

3. ARQUITETURA COM SUPERSET

3.1 Diagrama atualizado

┌─────────────────────────────────────────────────────────────────────┐
│                        USUÁRIO / FRONTEND                            │
│                                                                      │
│   BiWorkspace.tsx (8 tabs — 7 iguais, Advanced → Superset)         │
│   + QUALQUER outra página pode embedar um dashboard Superset        │
│                                                                      │
│   ┌────────────────────────────────────────────────────────────┐    │
│   │  <SupersetDashboard dashboardId="finance-overview" />      │    │
│   │  Componente React reutilizável em QUALQUER rota            │    │
│   └────────────────────────────────────────────────────────────┘    │
└─────────────────────────┬────────────────────────────────────────────┘
                          │
          ┌───────────────┼──────────────────────┐
          │               │                      │
          ▼               ▼                      ▼
┌──────────────┐ ┌────────────────┐ ┌──────────────────────┐
│ API BI Node  │ │ BI Engine Py   │ │ Superset (Python)    │
│ /api/bi/*    │ │ /api/bi-engine │ │ /superset/*          │
│ (sem mudança)│ │ :8004 (igual)  │ │ :8088                │
│              │ │                │ │                      │
│ + novo:      │ │                │ │ REST API v1          │
│ /api/superset│ │                │ │ Guest Token JWT      │
│ /guest-token │ │                │ │ SQL Lab              │
│              │ │                │ │ 50+ chart types      │
└──────────────┘ └────────────────┘ └──────────┬───────────┘
                                               │
                                               ▼
                                    ┌──────────────────────┐
                                    │  PostgreSQL           │
                                    │  arcadia (dados SOE)  │
                                    │  arcadia_superset     │
                                    │  (metadados Superset) │
                                    └──────────────────────┘

3.2 Dois bancos PostgreSQL

Banco: arcadia          → Dados do SOE (persons, products, sales_orders, etc.)
                          Superset conecta aqui em READ-ONLY para criar charts
Banco: arcadia_superset → Metadados do Superset (dashboards, queries, users)
                          Superset usa internamente

Ambos no mesmo servidor PostgreSQL, bancos separados. O docker-compose.yml já referencia arcadia_superset.


4. O QUE CONSTRUIR — Passo a Passo

4.1 Configuração do Superset (docker-compose.yml)

O container já existe. Faltam: init script, volumes, configuração de embedding.

# docker-compose.yml — seção superset (expandida)

superset:
  image: apache/superset:4.1.0
  restart: unless-stopped
  environment:
    SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-superset-secret-change-in-prod}
    DATABASE_URL: postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/arcadia_superset
    SUPERSET_WEBSERVER_PORT: 8088
    PYTHONPATH: /app/pythonpath
    # Para embedding funcionar:
    FEATURE_FLAGS: '{"EMBEDDED_SUPERSET": true, "ENABLE_TEMPLATE_PROCESSING": true}'
  ports:
    - "8088:8088"
  volumes:
    - ./docker/superset/superset_config.py:/app/pythonpath/superset_config.py
    - ./docker/superset/init.sh:/app/docker/init.sh
    - superset_home:/app/superset_home
  depends_on:
    db:
      condition: service_healthy
  command: ["/bin/bash", "/app/docker/init.sh"]
  profiles: [bi]
  networks:
    - arcadia

4.2 Arquivo de Configuração Superset

# docker/superset/superset_config.py

import os

# Chave secreta (mesma do .env)
SECRET_KEY = os.environ.get("SUPERSET_SECRET_KEY", "change-in-production")

# Banco de metadados do Superset
SQLALCHEMY_DATABASE_URI = os.environ.get(
    "DATABASE_URL",
    "postgresql://arcadia:arcadia123@db:5432/arcadia_superset"
)

# CORS — permite o gateway Arcádia (:5000) chamar a API
ENABLE_CORS = True
CORS_OPTIONS = {
    "supports_credentials": True,
    "allow_headers": ["*"],
    "resources": {r"/api/*": {"origins": "*"}},
}

# Embedding habilitado
FEATURE_FLAGS = {
    "EMBEDDED_SUPERSET": True,
    "ENABLE_TEMPLATE_PROCESSING": True,
    "ALERT_REPORTS": True,         # Alertas automáticos
    "DRILL_TO_DETAIL": True,       # Drill-down em charts
}

# Timeout de queries (30s por default, aumentar para análises pesadas)
SUPERSET_WEBSERVER_TIMEOUT = 300

# Cache (Redis se disponível, senão in-memory)
CACHE_CONFIG = {
    "CACHE_TYPE": "SimpleCache",
    "CACHE_DEFAULT_TIMEOUT": 300,  # 5 min
}

# Tema/branding Arcádia (logo, cores)
APP_NAME = "Arcádia Insights"
APP_ICON = "/static/arcadia-logo.png"

4.3 Init Script do Superset

#!/bin/bash
# docker/superset/init.sh — roda ao iniciar o container

set -e

echo "[Superset Init] Iniciando configuração..."

# Aguarda PostgreSQL
until psql "${DATABASE_URL}" -c "SELECT 1" > /dev/null 2>&1; do
  echo "[Superset Init] Aguardando PostgreSQL..."
  sleep 2
done

# Cria banco arcadia_superset se não existir
psql "postgresql://${PGUSER:-arcadia}:${PGPASSWORD:-arcadia123}@db:5432/postgres" \
  -c "CREATE DATABASE arcadia_superset OWNER arcadia;" 2>/dev/null || true

# Inicializa banco do Superset
superset db upgrade

# Cria usuário admin (só se não existir)
superset fab create-admin \
  --username "${SUPERSET_ADMIN_USER:-admin}" \
  --firstname "Arcádia" \
  --lastname "Admin" \
  --email "${SUPERSET_ADMIN_EMAIL:-admin@arcadia.app}" \
  --password "${SUPERSET_ADMIN_PASSWORD:-arcadia2026}" 2>/dev/null || true

# Carrega exemplos (opcional, comentar em prod)
# superset load_examples

# Inicializa roles e permissões
superset init

# Registra conexão com o banco Arcádia (dados do SOE)
python - <<'PYEOF'
from superset import create_app
from superset.extensions import db
from superset.models.core import Database
import os

app = create_app()
with app.app_context():
    existing = db.session.query(Database).filter_by(
        database_name="Arcádia Suite"
    ).first()
    if not existing:
        arcadia_db = Database(
            database_name="Arcádia Suite",
            sqlalchemy_uri=os.environ.get(
                "ARCADIA_DATABASE_URL",
                "postgresql://arcadia:arcadia123@db:5432/arcadia"
            ),
            expose_in_sqllab=True,
            allow_run_async=True,
            allow_csv_upload=False,
        )
        db.session.add(arcadia_db)
        db.session.commit()
        print("[Superset Init] Banco 'Arcádia Suite' registrado!")
    else:
        print("[Superset Init] Banco 'Arcádia Suite' já existe.")
PYEOF

echo "[Superset Init] Iniciando servidor..."
gunicorn \
  --bind "0.0.0.0:${SUPERSET_WEBSERVER_PORT:-8088}" \
  --access-logfile "-" \
  --error-logfile "-" \
  --workers 4 \
  --timeout 120 \
  --limit-request-line 0 \
  --limit-request-field_size 0 \
  "superset.app:create_app()"

4.4 Proxy Superset (substitui server/metabase/proxy.ts)

// server/superset/proxy.ts

import { Express, Response } from "express";
import { createProxyMiddleware } from "http-proxy-middleware";

const SUPERSET_HOST = process.env.SUPERSET_HOST || "localhost";
const SUPERSET_PORT = parseInt(process.env.SUPERSET_PORT || "8088", 10);
const SUPERSET_TIMEOUT = 60000;

export function setupSupersetProxy(app: Express): void {
  const target = `http://${SUPERSET_HOST}:${SUPERSET_PORT}`;

  const supersetProxy = createProxyMiddleware({
    target,
    changeOrigin: true,
    timeout: SUPERSET_TIMEOUT,
    proxyTimeout: SUPERSET_TIMEOUT,
    pathRewrite: { "^/superset": "" },
    on: {
      error: (err, _req, res) => {
        console.error("[Superset Proxy] Error:", err.message);
        if (res && typeof (res as Response).status === "function") {
          (res as Response).status(502).json({
            error: "Superset indisponível",
            message: "O Superset está iniciando. Tente novamente em alguns segundos.",
            target,
          });
        }
      },
      proxyRes: (proxyRes) => {
        const location = proxyRes.headers["location"];
        if (location && typeof location === "string" && location.startsWith("/")) {
          proxyRes.headers["location"] = `/superset${location}`;
        }
      },
    },
  });

  app.use("/superset", supersetProxy);
  console.log(`[Superset Proxy] Configurado -> /superset/* => ${target}`);
}

4.5 Rotas Superset — Guest Token para Embedding

Esta é a peça-chave para "funcionar em qualquer tela":

// server/superset/routes.ts

import type { Express, Request, Response } from "express";

const SUPERSET_HOST = process.env.SUPERSET_HOST || "localhost";
const SUPERSET_PORT = parseInt(process.env.SUPERSET_PORT || "8088", 10);
const SUPERSET_URL = `http://${SUPERSET_HOST}:${SUPERSET_PORT}`;
const SUPERSET_ADMIN_USER = process.env.SUPERSET_ADMIN_USER || "admin";
const SUPERSET_ADMIN_PASS = process.env.SUPERSET_ADMIN_PASSWORD || "arcadia2026";

// Cache do token de serviço (expira em 50 min)
let serviceToken: string | null = null;
let serviceTokenExpiry = 0;

async function getServiceToken(): Promise<string> {
  if (serviceToken && Date.now() < serviceTokenExpiry) return serviceToken;

  const resp = await fetch(`${SUPERSET_URL}/api/v1/security/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      username: SUPERSET_ADMIN_USER,
      password: SUPERSET_ADMIN_PASS,
      provider: "db",
      refresh: true,
    }),
  });

  if (!resp.ok) throw new Error("Falha ao autenticar no Superset");
  const data = await resp.json();
  serviceToken = data.access_token;
  serviceTokenExpiry = Date.now() + 50 * 60 * 1000; // 50 min
  return serviceToken!;
}

export function registerSupersetRoutes(app: Express): void {

  // Guest Token — para embedding em qualquer tela
  // O frontend chama este endpoint para obter um token temporário
  // e renderiza o dashboard via @superset-ui/embedded-sdk
  app.post("/api/superset/guest-token", async (req: Request, res: Response) => {
    try {
      if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });

      const { dashboardId } = req.body;
      if (!dashboardId) return res.status(400).json({ error: "dashboardId obrigatório" });

      const token = await getServiceToken();

      // Solicita guest token ao Superset
      const guestResp = await fetch(`${SUPERSET_URL}/api/v1/security/guest_token/`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${token}`,
        },
        body: JSON.stringify({
          user: {
            username: `arcadia_${req.user?.id}`,
            first_name: req.user?.name?.split(" ")[0] || "User",
            last_name: req.user?.name?.split(" ")[1] || "",
          },
          resources: [{ type: "dashboard", id: dashboardId }],
          rls: [],  // Row Level Security (futuro: por tenantId)
        }),
      });

      if (!guestResp.ok) {
        const err = await guestResp.text();
        return res.status(502).json({ error: "Falha ao gerar guest token", detail: err });
      }

      const { token: guestToken } = await guestResp.json();
      res.json({ token: guestToken, supersetUrl: "/superset" });
    } catch (err: any) {
      res.status(502).json({ error: err.message });
    }
  });

  // Lista dashboards disponíveis (para o seletor na UI)
  app.get("/api/superset/dashboards", async (req: Request, res: Response) => {
    try {
      if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" });

      const token = await getServiceToken();
      const resp = await fetch(`${SUPERSET_URL}/api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc)`, {
        headers: { "Authorization": `Bearer ${token}` },
      });

      if (!resp.ok) return res.status(502).json({ error: "Superset indisponível" });
      const data = await resp.json();
      res.json(data.result || []);
    } catch (err: any) {
      res.status(502).json({ error: err.message });
    }
  });

  // Health check
  app.get("/api/superset/health", async (_req: Request, res: Response) => {
    try {
      const resp = await fetch(`${SUPERSET_URL}/health`);
      res.json({ online: resp.ok, url: SUPERSET_URL });
    } catch {
      res.json({ online: false, url: SUPERSET_URL });
    }
  });
}

4.6 Componente React — SupersetDashboard (reutilizável em QUALQUER tela)

// client/src/components/SupersetDashboard.tsx

import { useEffect, useRef, useState } from "react";
import { embedDashboard } from "@superset-ui/embedded-sdk";

interface SupersetDashboardProps {
  dashboardId: string;
  height?: string | number;
  className?: string;
}

export function SupersetDashboard({
  dashboardId,
  height = "calc(100vh - 300px)",
  className = "",
}: SupersetDashboardProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!containerRef.current) return;

    let mounted = true;
    setLoading(true);
    setError(null);

    embedDashboard({
      id: dashboardId,
      supersetDomain: window.location.origin + "/superset",
      mountPoint: containerRef.current,

      // Busca guest token do gateway Arcádia
      fetchGuestToken: async () => {
        const resp = await fetch("/api/superset/guest-token", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ dashboardId }),
          credentials: "include",
        });
        if (!resp.ok) throw new Error("Falha ao obter token");
        const { token } = await resp.json();
        return token;
      },

      dashboardUiConfig: {
        hideTitle: true,           // Título próprio no Arcádia
        hideChartControls: false,  // Manter controles de chart
        filters: {
          visible: true,           // Mostrar filtros
          expanded: false,
        },
      },
    }).then(() => {
      if (mounted) setLoading(false);
    }).catch((err) => {
      if (mounted) {
        setError(err.message);
        setLoading(false);
      }
    });

    return () => { mounted = false; };
  }, [dashboardId]);

  if (error) {
    return (
      <div className="flex items-center justify-center h-48 text-red-500 text-sm">
        Superset indisponível: {error}
      </div>
    );
  }

  return (
    <div className={`relative ${className}`} style={{ height }}>
      {loading && (
        <div className="absolute inset-0 flex items-center justify-center bg-white/80 z-10">
          <div className="text-center text-[#1f334d]">
            <div className="w-8 h-8 border-4 border-[#c89b3c] border-t-transparent rounded-full animate-spin mx-auto mb-2" />
            <p className="text-sm">Carregando Arcádia Insights...</p>
          </div>
        </div>
      )}
      <div ref={containerRef} className="w-full h-full" />
    </div>
  );
}

Uso em QUALQUER página:

// Exemplo: na página /financeiro
import { SupersetDashboard } from "@/components/SupersetDashboard";

// Dashboard financeiro embeddado diretamente no módulo Financeiro
<SupersetDashboard dashboardId="financial-overview" height={500} />

// Exemplo: na página /contabil
<SupersetDashboard dashboardId="dre-mensal" />

// Exemplo: no Dashboard principal (/)
<SupersetDashboard dashboardId="executive-summary" height={400} />

4.7 BiWorkspace.tsx — Advanced tab (única mudança na UI)

// Substituir a tab Advanced (linhas 2918-2945)

<TabsContent value="advanced" className="mt-0">
  <div className="space-y-4">
    <div className="flex items-center justify-between">
      <div>
        <h2 className="text-lg font-semibold text-[#1f334d]">Arcádia Insights  Apache Superset</h2>
        <p className="text-sm text-gray-500">
          SQL Lab avançado, 50+ tipos de gráfico, dashboards interativos
        </p>
      </div>
      <a
        href="/superset"
        target="_blank"
        rel="noopener noreferrer"
        className="inline-flex items-center gap-2 px-4 py-2 bg-[#1f334d] text-white rounded-lg hover:bg-[#2a4466] transition-colors text-sm"
      >
        <ArrowRight className="w-4 h-4" /> Abrir em Nova Aba
      </a>
    </div>

    {/* Dashboard selecionável */}
    <SupersetDashboardSelector />
  </div>
</TabsContent>

5. TABELAS E VARIÁVEIS

5.1 Nenhuma tabela nova no schema Arcádia

O Superset usa seu próprio banco (arcadia_superset) para metadados internos. As tabelas do Arcádia não mudam.

5.2 Novas variáveis de ambiente

# .env — adicionar/substituir:

# Superset (substitui METABASE_*)
SUPERSET_HOST=superset               # nome do container Docker
SUPERSET_PORT=8088
SUPERSET_SECRET_KEY=                 # gerar: openssl rand -hex 32
SUPERSET_ADMIN_USER=admin
SUPERSET_ADMIN_EMAIL=admin@arcadia.app
SUPERSET_ADMIN_PASSWORD=             # senha forte em prod

# URL do banco Arcádia para o Superset acessar (read-only ideal)
ARCADIA_DATABASE_URL=postgresql://arcadia_ro:pass@db:5432/arcadia

# Remover (não são mais necessários):
# METABASE_HOST
# METABASE_PORT
# METASET_ADMIN_EMAIL
# METASET_ADMIN_PASSWORD

6. ROADMAP — Fases de Implementação

Fase 1 — Infraestrutura (2-3 dias)

[ ] Criar docker/superset/superset_config.py
[ ] Criar docker/superset/init.sh
[ ] Atualizar docker-compose.yml (expandir seção superset existente)
[ ] Adicionar volumes: superset_home no docker-compose
[ ] Testar: docker compose --profile bi up superset
[ ] Verificar: banco arcadia_superset criado, admin funciona, :8088 acessível

Fase 2 — Gateway (1-2 dias)

[ ] Criar server/superset/proxy.ts (substitui server/metabase/proxy.ts)
[ ] Criar server/superset/routes.ts (guest token, lista dashboards, health)
[ ] Registrar no server/routes.ts: setupSupersetProxy() + registerSupersetRoutes()
[ ] Remover/comentar setupMetabaseProxy() e registerMetaSetRoutes()
[ ] Testar: GET /api/superset/health → { online: true }
[ ] Testar: POST /api/superset/guest-token → token JWT

Fase 3 — Frontend (2-3 dias)

[ ] npm install @superset-ui/embedded-sdk
[ ] Criar client/src/components/SupersetDashboard.tsx
[ ] Atualizar BiWorkspace.tsx Advanced tab (substituir iframe MetaSet)
[ ] Testar: dashboard embeddado na Advanced tab
[ ] Criar 3 dashboards base no Superset:
    - executive-summary (KPIs gerais)
    - financial-overview (Financeiro)
    - dre-mensal (DRE)

Fase 4 — Embedding em outras telas (1 semana)

[ ] Embedar SupersetDashboard no /financeiro (tab Análise)
[ ] Embedar SupersetDashboard no /contabil (tab DRE/Balanço)
[ ] Embedar SupersetDashboard no SOE (tab Dashboard)
[ ] Embedar SupersetDashboard no Home / Dashboard principal
[ ] RLS por tenantId (Row Level Security nos dashboards)

Fase 5 — Migração de dashboards (conforme necessário)

[ ] Recriar no Superset os dashboards que existiam no MetaSet (se houver)
[ ] Configurar alertas automáticos (Superset Alerts & Reports)
[ ] Criar dashboards para cada domínio do SOE:
    - Vendas, Compras, Fiscal, Pessoas, Estoque

7. DIAGRAMA BI ATUALIZADO (completo)

┌────────────────────────────────────────────────────────────────────┐
│                     ARCÁDIA BI STACK v2                             │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  FRONTEND REACT — Pode usar em QUALQUER rota                 │  │
│  │                                                               │  │
│  │  BiWorkspace.tsx     → 8 tabs (7 iguais + Advanced=Superset) │  │
│  │  <SupersetDashboard> → componente reutilizável               │  │
│  │  Guest Token API     → /api/superset/guest-token             │  │
│  └────────────────────────────┬─────────────────────────────────┘  │
│                               │                                     │
│         ┌─────────────────────┼──────────────────────┐             │
│         │                     │                      │             │
│  ┌──────▼──────┐     ┌────────▼───────┐   ┌─────────▼──────────┐  │
│  │  API BI     │     │  BI Engine     │   │ Apache Superset     │  │
│  │  (Node.js)  │     │  Python:8004   │   │ Python:8088         │  │
│  │  sem mudança│     │  sem mudança   │   │                     │  │
│  │             │     │                │   │ SQL Lab             │  │
│  │  CRUD       │     │ SQL+Charts     │   │ 50+ chart types     │  │
│  │  Upload/ETL │     │ Micro-BI       │   │ Guest Token (embed) │  │
│  │  Staging    │     │ Análise Pandas │   │ Alerts & Reports    │  │
│  │  Backups    │     │ Cache 5min     │   │ REST API v1         │  │
│  └─────┬───────┘     └────────┬───────┘   └─────────┬───────────┘  │
│        │                      │                     │              │
│        └──────────────────────┼─────────────────────┘              │
│                               │                                     │
│                    ┌──────────▼──────────┐                         │
│                    │    PostgreSQL        │                         │
│                    │                     │                         │
│                    │  arcadia            │ ← Dados SOE (read-only) │
│                    │  arcadia_superset   │ ← Metadados Superset    │
│                    └────────────────────┘                          │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  CIENTISTA (Python) — IA/ML: análise, padrões, insights      │  │
│  │  ASSISTENTE BI (GPT) — chat sobre dados                      │  │
│  │  (ambos sem mudança)                                          │  │
│  └──────────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────────┘

8. RESUMO — O que muda, o que fica

Componente Ação Esforço
server/metabase/proxy.ts Substituir por server/superset/proxy.ts 30 min
server/metaset/routes.ts Substituir por server/superset/routes.ts 2h
server/metaset/client.ts Remover (lógica vai para routes.ts) 5 min
docker-compose.yml Superset Expandir configuração existente 1h
docker/superset/superset_config.py Criar (novo arquivo) 30 min
docker/superset/init.sh Criar (novo arquivo) 1h
BiWorkspace.tsx Advanced tab Substituir iframe MetaSet 1h
SupersetDashboard.tsx Criar componente (novo) 2h
npm install @superset-ui/embedded-sdk Instalar dependência 5 min
BI Engine Python (:8004) Não muda nada
API BI Node /api/bi/* Não muda nada
Upload/ETL/Staging Não muda nada
7 tabs do BiWorkspace Não muda nada
Tabelas do banco Não muda nada

Total estimado: 1-2 dias de implementação real (a infra já está 80% pronta).


9. VANTAGEM ESTRATÉGICA

O Apache Superset não é apenas "trocar Metabase".

É a virada de chave que permite que o BI deixe de ser uma página separada e vire um componente vivo que aparece dentro de cada módulo do Arcádia:

  • O usuário está no módulo Financeiro → vê o dashboard financeiro ali mesmo
  • O usuário está no módulo Contábil → vê o DRE ali mesmo
  • O usuário está no SOE → vê os KPIs do negócio ali mesmo

Sem abrir nova aba. Sem sair do contexto. Sem re-autenticar. O Superset renderiza no lugar exato onde o usuário está.


PLANO_BI_SUPERSET.md — Arcádia Suite v3.0 Gerado em: 2026-03-16 — Baseado na análise do MAPA_BI_ARCADIA.md (Replit) + código real