DEV Community

Fabio Nobre
Fabio Nobre

Posted on

O fantasma que assombra o PDF da Fecombustíveis: um robô que transforma a tributação dos combustíveis em XML

Hacktoberfest Open-Source AI Challenge: Ghost in the Machine Submission 👻🌼

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine

Transparência antes de tudo: este projeto usa o Google Gemini (API fechada) para ler o PDF — não um modelo open-weight. Conto abaixo por que fiz essa escolha, onde o código é aberto e o que falta para trocar por um modelo aberto.

What I Built

Todo mês, sem aviso de horário, aparece um PDF no site da Fecombustíveis: a "Carga tributária estadual" dos combustíveis. Ali estão o PMPF, a alíquota de ICMS, o tributo estadual e o federal de gasolina, diesel, etanol e GNV para as 27 UFs.

Quem emite nota fiscal de combustível no Brasil precisa desses números para a Lei da Transparência (o "valor aproximado dos tributos" no cupom). No nosso caso, eles alimentam o XML TransparenciaTributos usado no nosso sistema de emissão fiscal. Antes, alguém abria o PDF — com as tabelas em imagem — e digitava dezenas de valores à mão.

O Transparência de Tributos – Combustíveis é o "fantasma na máquina" que faz isso sozinho:

  • 👻 Assombra a página da Fecombustíveis a cada 8 horas procurando o PDF novo;
  • 📄 Lê o PDF com IA e devolve os valores por combustível e UF;
  • 🧮 Confere as contas (PMPF × alíquota = ICMS; no GNV, PMPF × 9,25% = PIS/COFINS) e se recusa a gravar se algo não fechar;
  • 🕯️ Honra o passado: o bloco de vigência antigo não é apagado — ele "termina" no dia anterior (ex.: até 30/09/2026) e nasce um bloco novo (01/10/2026 até 2050). O histórico de cada tributo fica preservado no XML;
  • 🔔 Avisa por e-mail e no Discord quando a tributação muda;
  • 🌐 Publica uma página pública com os tributos por estado.

Minha leitura do tema: o PDF mensal é um fantasma que aparece quando quer, e a ideia foi deixar a máquina vigiando por mim — sem perder a memória dos meses anteriores, que é o que o fisco pede quando confere uma nota antiga.

Demo

Fluxo no site:

  1. Passo 1 – o PDF mais recente já vem selecionado, com o XML do mês anterior como base.
  2. Passo 2 – a IA extrai os valores; o que mudou fica amarelo (com o valor antigo embaixo) e conta que não fecha ganha borda vermelha. Tudo é editável na tabela.
  3. Passo 3 – Gerar XML, Baixar XML e, se quiser, Baixar Excel.

Code

Transparência de Tributos – Combustíveis

Site estático (GitHub Pages) que gera todo mês o XML TransparenciaTributos a partir do PDF "Carga tributária estadual" da Fecombustíveis.

Site: https://imextecnologia.github.io/tributacao/ · Tributos por estado: https://imextecnologia.github.io/tributacao/tributos.html

Como funciona

  1. Busca do PDF – a cada 8 horas um workflow procura o PDF novo na página da Fecombustíveis, salva em pdf/ e já gera o XML do mês em xml/ (veja Busca automática do PDF).
  2. Leitura com IA – o site envia o PDF ao Google Gemini, que devolve os valores por combustível e UF.
  3. Comparação – os valores são comparados com o XML do mês anterior (em xml/) e o que mudou fica destacado.
  4. Geração – para cada combustível que mudou, o bloco antigo termina no dia anterior (ex.: 30/09/2026) e é criado um bloco novo (ex.: 01/10/2026 até 01/01/2050). Combustíveis sem mudança ficam como estão Também é possível baixar uma planilha…

How I Built It

Sem servidor. É um site estático no GitHub Pages; tudo roda no navegador ou num job do GitHub Actions.

Busca automática (.github/workflows/buscar-pdf.yml, cron 0 */8 * * *):

  1. scripts/buscar-pdf.js lê a página da Fecombustíveis e pega o link do PDF mais recente pela data no nome ("10 OUTUBRO 2026" vem antes de "01 OUTUBRO 2026"). Se a página falhar, tenta o link no padrão do mês atual e do próximo.
  2. scripts/gerar-xml.js gera o XML do PDF novo usando o mesmo prompt, produtos e UFs do index.html — o script extrai essas constantes direto do HTML, então site e robô nunca divergem.
  3. scripts/buscar-painel.js relê o quadro oficial de percentuais e atualiza painel.json.
  4. Se algo mudou: commit, publicação do site e aviso por e-mail/Discord com o diff.

A IA. O PDF vai para o Gemini com um prompt que pede somente JSON num formato fixo (gasolina/diesel com valor único; etanol e GNV por UF). O site tenta uma lista de modelos em ordem (gemini-flash-latest → gemini-flash-lite-latest → gemini-2.5-flash → gemini-2.5-flash-lite), com nova tentativa em 429/503.

A rede de segurança importa mais que o modelo. IA lendo tabela em imagem erra dígito. Por isso:

// PMPF × alíquota precisa bater com o ICMS (etanol no MT usa base de 50%)
const base = (p.key === "etanol" && row.uf === "MT") ? row.pmpf * 0.5 : row.pmpf;
if (Math.abs(base * row.aliquota / 100 - row.estadual) > 0.0015) err.estadual = true;
// GNV: PIS/COFINS = PMPF × 9,25%
if (p.key === "gnv" && Math.abs(row.pmpf * 0.0925 - row.federal) > 0.0015) err.federal = true;
Enter fullscreen mode Exit fullscreen mode

No Actions, se qualquer conta não fecha, o XML não é gravado — o aviso pede revisão humana no site, e as execuções seguintes tentam de novo. Uma alucinação nunca vira nota fiscal.

Economia de chamadas: se o XML do mês já existe em xml/, o site carrega direto; o resultado da IA fica salvo no navegador, então recarregar a página não chama o modelo de novo.

Why Does Open Innovation Matter?

Sendo honesto: a parte de IA aqui não é aberta. Escolhi o Gemini porque o plano gratuito lê PDF com tabela em imagem de forma confiável, não exige cartão e o volume é mínimo (uma leitura por mês). Para um site estático sem servidor, isso resolveu.

Mas o projeto mostra exatamente onde um caminho fechado incomoda:

  • A chave fica exposta no index.html publicado (restrita por referenciador e API, mas ainda assim pública). Um modelo local não teria chave nenhuma.
  • Dependência de fornecedor: precisei de uma lista de fallbacks porque modelos somem ou mudam de nome. Um modelo open-weight com versão fixa daria a mesma resposta amanhã — algo valioso quando o resultado vai para documento fiscal.
  • Dados: no plano gratuito o Google pode usar o conteúdo enviado. Hoje só vai o PDF público da Fecombustíveis, então não há problema — mas eu não poderia fazer o mesmo com documentos de clientes.

O que é aberto: todo o código, o workflow, as regras de validação e o histórico de XMLs estão no GitHub, públicos. Qualquer posto, contador ou software fiscal pode copiar, auditar as contas ou usar os XMLs.

Próximo passo: como o prompt já pede JSON estrito e a validação aritmética independe do modelo, a troca é isolada numa função (chamarGemini). O plano é testar um modelo de visão open-weight (por exemplo, Gemma) rodando dentro do próprio GitHub Actions ou localmente e comparar a taxa de "contas que não fecham" com o Gemini.

The Spirit of It

No Día de los Muertos a gente lembra quem veio antes. No XML de tributos, nada é apagado: cada mês que passa vira um bloco com data de fim, e o novo começa no dia seguinte. Quando alguém abrir uma nota de setembro daqui a cinco anos, os valores de setembro ainda estarão lá.

E o fantasma? Ele aparece às 21h, 5h e 13h (horário de Brasília), olha a página, e na maioria das vezes vai embora em silêncio. Quando a tributação muda, ele deixa um recado no Discord. 👻

Prize Categories

  • Best Use of GitHub Copilot — a automação inteira (busca, geração do XML, publicação e avisos) roda em GitHub Actions.

Top comments (0)