Pular para conteúdo

Etapa 04 — Cadastro de veículo e frota

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-06-15

Estende o parsing da etapa 01 e o UPSERT diff-aware da etapa 02 com as colunas veiculares e uma regra de preservação específica.

1. Contexto e escopo

As planilhas RESULTADO_TRANSPORTE_* recentes trazem, além dos campos históricos, frota (própria vs terceiros) e identificação do caminhão (marca, modelo, anos). O processamento mapeia essas colunas e as persiste preservando correções manuais de cadastro feitas direto no banco.

Dentro do escopo: mapeamento das colunas novas; parse do ano AAAA/AAAA; backfill do cadastro veicular nas linhas existentes; defesa contra entrada inválida; preservação no reprocessamento.

Fora do escopo: uma entidade de veículo normalizada — hoje os dados veiculares vivem desnormalizados nas colunas dos próprios fatos bi_faturamento/bi_movimento, chaveados por placa (não há tabela mestre de frota persistente).

2. Modelo de dados

Cinco colunas adicionadas em ambos os fatos (V3__bi_veiculo_frota.sql):

erDiagram
    bi_faturamento {
        varchar tipo_frota "aba 20 col 33"
        varchar marca_veiculo "col 34"
        varchar modelo_veiculo "col 35"
        smallint ano_construcao "col 36, esq. da barra"
        smallint ano_modelo "col 36, dir. da barra"
    }
    bi_movimento {
        varchar tipo_frota "aba 30 col 27"
        varchar marca_veiculo "col 28"
        varchar modelo_veiculo "col 29"
        smallint ano_construcao "col 30, esq."
        smallint ano_modelo "col 30, dir."
    }

Tipos: tipo_frota VARCHAR(20), marca_veiculo/modelo_veiculo VARCHAR(80), ano_construcao/ano_modelo SMALLINT (todas nullable). Mapeamento 0-based: aba 20 → 33/34/35/36; aba 30 → 27/28/29/30. O ano é uma string AAAA/AAAA (ex.: 2006/2007): AnosVeiculoParser grava os 4 dígitos à esquerda em ano_construcao e os à direita em ano_modelo; formato inválido → ambos null.

O backfill do V10 gravava um relatório de não-matched numa tabela xls_veiculo_backfill_relatorio — one-time, lida por nada em runtime, removida em V15. O backfill em si (UPDATE nos bi_*) permanece.

Defesa veicular (6 colunas em xls_processamento, V11): defesa_marca_ausente_qtd, defesa_modelo_ausente_qtd, defesa_ano_invalido_qtd, defesa_tipo_frota_ausente_qtd, defesa_placa_malformada_qtd (INTEGER) + xls_legado (BOOLEAN).

3. Fluxos principais

  • Mapeamento + defesa: ao ler cada linha das abas 20/30, o ExcelProcessor registra as ocorrências de marca/modelo/ano/tipo_frota ausentes e placa malformada nas contagens defesa_*_qtd — sem bloquear o lote (entrada ruim vira métrica + evento no Bugsink, não erro).
  • Backfill (V10__backfill_veiculos.sql): a partir de um mestre veicular (~3100 placas, planilha 20260525-veiculos-final.xlsx carregada numa TEMP TABLE ON COMMIT DROP), faz UPDATE-join idempotente preenchendo as colunas veiculares nas linhas existentes de bi_faturamento/bi_movimento, sem sobrescrever valores já gravados não-vazios. (A migration está congelada; o gerador one-shot que a produziu foi removido — ver CHANGELOG.)
  • Preservação (decisão 0012): as cinco colunas estão em ColunasConteudo.PRESERVAR_SE_VAZIO_NA_ENTRADA. No UPSERT, entrada "vazia" (NULL / '' / 0 / placeholders 'SEM MARCA'/'SEM MODELO') não sobrescreve o valor gravado, via COALESCE(NULLIF(EXCLUDED.col, <sentinela>), <tabela>.col) — e também não dispara UPDATE (sem churn de xmin/PowerSync, consistente com a etapa 02). Correções de cadastro feitas direto no banco sobrevivem a reprocessamentos; em troca, esses campos só são limpos por script no banco, nunca por upload.

4. Decisões

Nº Decisão
0012 Preservação de cadastro de veículo no UPSERT

Relacionado: o mecanismo de UPSERT diff-aware é o da etapa 02.

5. Riscos

  • Cliente muda o layout das colunas (índices) → o parse pega célula errada; mitigado por testes com fixtures reais por período e pelas métricas de defesa.
  • Upload com colunas vazias sobrescrevendo cadastro bom → resolvido pela preservação (decisão 0012).