Pular para o conteúdo principal

Investigação — Títulos: mart não reconcilia com a fato

Data: 2026-08-06 Contexto: relatório de Títulos do Belite (grupo CAMINHO, grupo_sk = 6843126268568520649), período junho/2026. Origem: validação de dados após correção de envios/clientes no Monitor F&I (mesma família de relatórios).


Sintoma

Os KPIs (big numbers) e a tabela detalhada do relatório de Títulos não batem:

OrigemTabelaA Receber — junho/2026
Mart fandi_kpi_recebimento_diaKPIs e gráficoR$ 36,68M
Fato fandi_fat_recebimentoTabela detalhada e contagemR$ 30,00M
DiferençaR$ 6,68M

No app, os KPIs/gráfico leem o mart e a tabela detalhada lê a fato — por isso a divergência aparece na tela.


Achado 1 — Linhas com departamento NULL no mart

O mart fandi_kpi_recebimento_dia tem, em junho/2026 para esse grupo, 152 linhas com departamento NULL somando R$ 5,90M. Elas se dividem em dois padrões distintos:

PadrãoBucketsValorDescrição
Rollup duplicado98R$ 3,13MA linha NULL é exatamente igual à soma das linhas por departamento do mesmo bucket (fabricante_sk × loja_sk × banco_sk × recebimento_tipo_sk × dia). É o mesmo padrão de rollup da ETL já identificado no mart do Monitor F&I.
Dado real sem depto54R$ 2,77MNão existe linha de departamento correspondente no bucket — dado que realmente não tem classificação de departamento.

Alguns buckets com NULL têm também banco_sk NULL.

Importante: diferente do Monitor F&I, aqui não dá pra simplesmente filtrar departamento IS NOT NULL no app — isso funcionou lá porque TODO o NULL era rollup. Aqui, o filtro cortaria R$ 2,77M de dado real.


Achado 2 — O mart excede a fato mesmo sem as linhas NULL

Removendo as linhas NULL de departamento, o mart (R$ 30,75M) já é maior que a fato (R$ 30,0M) em ~R$ 761k. Comparação dia a dia mostra que o mart é maior que a fato em quase todos os dias, com picos de até R$ 2,2M num único dia (2026-06-09).

Ou seja: os rollups com NULL explicam só ~metade da divergência (R$ 3,13M dos R$ 6,68M). Existe discrepância adicional não relacionada a departamento.


Observação sobre a fato

A fato fandi_fat_recebimento tem uma linha de finalidade -1 (dimensão fandi_dim_recebimento_tipo sem match) no período. Na query do app, qualquer finalidade que não seja 'Pagamento' é contada como A Receber — consistente, mas vale conferir se essa linha -1 é esperada ou é dado órfão.


Pergunta aberta pro time de dados

O código-fonte do app documenta que o mart foi "validado linha a linha contra a fato (mesmos totais)" antes de entrar em produção — mas os dados atuais não reconciliam. Precisamos descobrir qual lado está certo:

  1. O mart está duplicado? — os 98 buckets de rollup com NULL são evidência forte de duplicação na ETL. Se confirmado, a correção é na ETL: não gerar a linha de rollup com departamento NULL.
  2. A fato está incompleta? — o pipeline já perdeu dados antes (ex.: a fandi_fat_envio não tem NENHUM registro de abril/2026 para esse grupo). Se a fato está perdendo recebimentos, o mart pode estar certo e a tabela detalhada errada.

O que precisa ser verificado:

  • Reconciliar o mart contra a origem no ERP (não contra a própria fato) pra saber quem é a fonte da verdade.
  • Confirmar se a carga da fandi_fat_recebimento está completa para o grupo/período (histórico de perda em abril sugere revisar a janela de carga).
  • Revisar a regra de geração de linhas com departamento NULL na ETL do fandi_kpi_recebimento_dia.

Enquanto a reconciliação não fechar, não há fix de app seguro: qualquer filtro do lado do app (ex.: excluir NULL de departamento) corrige uma parte e corta outra.


Queries de validação usadas nesta investigação

-- Mart: total por finalidade, separando NULL vs não-NULL de departamento
SELECT tp.finalidade_recebimento,
CASE WHEN k.departamento IS NULL THEN 'depto_null' ELSE 'depto_def' END AS depto,
sum(k.valor_total) AS valor
FROM dw_concessionarias_mart.fandi_kpi_recebimento_dia k
LEFT JOIN dw_concessionarias_gold.fandi_dim_recebimento_tipo tp
ON tp.recebimento_tipo_sk = k.recebimento_tipo_sk
WHERE k.grupo_sk = '6843126268568520649' AND k.ano = 2026 AND k.mes = 6
GROUP BY 1, 2;

-- Fato: total por finalidade
SELECT tp.finalidade_recebimento, sum(r.valor_recebimento)
FROM dw_concessionarias_gold.fandi_fat_recebimento r
LEFT JOIN dw_concessionarias_gold.fandi_dim_recebimento_tipo tp
ON tp.recebimento_tipo_sk = r.recebimento_tipo_sk
WHERE r.grupo_sk = '6843126268568520649' AND r.ano = 2026 AND r.mes = 6
GROUP BY 1;

-- Classificar as linhas NULL do mart em rollup (duplica) vs dado real
WITH por_depto AS (
SELECT fabricante_sk, loja_sk, banco_sk, recebimento_tipo_sk, data_faturamento,
sum(valor_total) AS soma
FROM dw_concessionarias_mart.fandi_kpi_recebimento_dia
WHERE grupo_sk = '6843126268568520649' AND ano = 2026 AND mes = 6
AND departamento IS NOT NULL
GROUP BY 1, 2, 3, 4, 5
),
null_buckets AS (
SELECT n.fabricante_sk, n.loja_sk, n.banco_sk, n.recebimento_tipo_sk, n.data_faturamento,
sum(n.valor_total) AS soma_null, d.soma AS soma_deptos
FROM dw_concessionarias_mart.fandi_kpi_recebimento_dia n
LEFT JOIN por_depto d USING (fabricante_sk, loja_sk, banco_sk, recebimento_tipo_sk, data_faturamento)
WHERE n.grupo_sk = '6843126268568520649' AND n.ano = 2026 AND n.mes = 6
AND n.departamento IS NULL
GROUP BY 1, 2, 3, 4, 5, d.soma
)
SELECT CASE WHEN abs(soma_null - soma_deptos) < 0.01 THEN 'ROLLUP (duplica)' ELSE 'DADO REAL (null)' END AS tipo,
count(*) AS buckets, sum(soma_null) AS valor
FROM null_buckets
GROUP BY 1;

-- Comparação dia a dia mart vs fato
WITH mart_dia AS (
SELECT data_faturamento, sum(valor_total) AS valor
FROM dw_concessionarias_mart.fandi_kpi_recebimento_dia
WHERE grupo_sk = '6843126268568520649' AND ano = 2026 AND mes = 6
GROUP BY 1
),
fato_dia AS (
SELECT to_date(data_faturamento_sk::text, 'YYYYMMDD') AS dia, sum(valor_recebimento) AS valor
FROM dw_concessionarias_gold.fandi_fat_recebimento
WHERE grupo_sk = '6843126268568520649' AND ano = 2026 AND mes = 6 AND data_faturamento_sk > 0
GROUP BY 1
)
SELECT m.data_faturamento, m.valor AS mart, f.valor AS fato,
round(m.valor - coalesce(f.valor, 0)) AS diff
FROM mart_dia m
FULL OUTER JOIN fato_dia f ON f.dia = m.data_faturamento
ORDER BY m.data_faturamento NULLS LAST;

Nota: para o Monitor F&I (mart fandi_kpi_contrato_dia), a mesma regra de rollup com departamento NULL já foi confirmada e tratada com departamento IS NOT NULL no app — lá todo NULL era rollup. O caso de Títulos é diferente e precisa de decisão antes de qualquer filtro.