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:
| Origem | Tabela | A Receber — junho/2026 |
|---|---|---|
Mart fandi_kpi_recebimento_dia | KPIs e gráfico | R$ 36,68M |
Fato fandi_fat_recebimento | Tabela detalhada e contagem | R$ 30,00M |
| Diferença | R$ 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ão | Buckets | Valor | Descrição |
|---|---|---|---|
| Rollup duplicado | 98 | R$ 3,13M | A 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 depto | 54 | R$ 2,77M | Nã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 NULLno 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:
- 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. - A fato está incompleta? — o pipeline já perdeu dados antes (ex.: a
fandi_fat_envionã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_recebimentoestá 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 NULLna ETL dofandi_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 comdepartamento NULLjá foi confirmada e tratada comdepartamento IS NOT NULLno app — lá todo NULL era rollup. O caso de Títulos é diferente e precisa de decisão antes de qualquer filtro.