Skip to content

fix: migra fonte do /pix/v1/participants para snapshot versionado (BCB descontinuou o CSV) - #892

Open
felipeflfranca wants to merge 4 commits into
BrasilAPI:mainfrom
felipeflfranca:claude/sweet-curie-af9961
Open

fix: migra fonte do /pix/v1/participants para snapshot versionado (BCB descontinuou o CSV)#892
felipeflfranca wants to merge 4 commits into
BrasilAPI:mainfrom
felipeflfranca:claude/sweet-curie-af9961

Conversation

@felipeflfranca

Copy link
Copy Markdown
Contributor

📋 Descrição

GET /api/pix/v1/participants está retornando 500 em produção: o BCB descontinuou o CSV público de participantes do Pix — a URL antiga responde 401 (WAF) para o dia corrente e 404 para dias anteriores, mesmo com User-Agent/Referer/cookies de navegador. A lista oficial agora é publicada apenas em PDF, disponível somente para os ~2 dias mais recentes, e não existe dataset equivalente no OData (Pix_DadosAbertos, SPI) nem no CKAN de dados abertos do BCB.

Este PR migra a fonte da rota para um snapshot versionado no repositório, extraído do PDF oficial fora do request-time — mesmo padrão já usado em scripts/generate-bank-headquarters-snapshot.js:

  • scripts/generate-pix-participants-snapshot.js: para cada data (hoje e até 7 dias para trás), tenta primeiro o CSV original (com validação de cabeçalho — se o BCB o reativar, a fonte melhor volta a ser usada automaticamente) e cai para o PDF, extraindo a tabela de participantes ativos com pdfjs-dist (devDependency — nunca roda em request-time). Grava o snapshot já no formato de resposta da rota, com validações de sanidade que fazem o CI falhar alto se o layout do BCB mudar (mínimo de 700 participantes, ISPB ^\d{8}$, sem ISPBs duplicados, modalidades/tipos dentro do conjunto conhecido). O campo source_format no metadata-latest.json registra qual fonte cada snapshot usou.
  • services/pix/snapshots/latest.json: snapshot com 880 participantes, atualizado em dias úteis via GitHub Action (pix-participants-snapshot.yml), que só commita quando a lista realmente muda.
  • A rota passa a servir o snapshot: contrato de resposta 100% preservado (ispb, nome, nome_reduzido, modalidade_participacao, tipo_participacao, inicio_operacao), incluindo zeros à esquerda no ISPB e o erro 500 com PIX_LIST_ERROR. O campo inicio_operacao já era null desde nov/2025, quando o BCB removeu essa informação da fonte.
  • Validação da extração contra um CSV arquivado do formato recente (Wayback Machine, 10/06/2026): 879/880 ISPBs em comum, 0 divergências de modalidade_participacao, 3 divergências de tipo_participacao (migrações reais Indireta→Direta no período) e divergências de nome correspondentes a renomeações reais de instituições (conferidas no PDF atual).

Bônus: a rota deixa de depender da disponibilidade do BCB em request-time — o 500 intermitente de fim de semana/feriado desaparece junto.

🎯 Tipo de Mudança

  • 🐛 Correção de bug (mudança que corrige um problema)
  • ✨ Nova funcionalidade (mudança que adiciona funcionalidade)
  • 💥 Breaking change (correção ou funcionalidade que causa quebra de compatibilidade)
  • 📝 Documentação (mudanças apenas em documentação)
  • ♻️ Refatoração (mudança que não corrige bug nem adiciona funcionalidade)
  • ⚡ Performance (mudança que melhora performance)
  • ✅ Testes (adiciona ou corrige testes)
  • 🔧 Configuração (mudanças em configuração ou build)

⚠️ Checklist de Compatibilidade (CRÍTICO)

  • ✅ Não remove campos de respostas de API existentes
  • ✅ Não renomeia campos de respostas de API existentes
  • ✅ Não muda tipos de dados de campos existentes (string → number, etc.)
  • ✅ Não muda o formato de URLs de endpoints existentes
  • ✅ Não muda códigos de status HTTP de endpoints existentes
  • ✅ Se fez mudanças incompatíveis, criei uma nova versão (v2, v3, etc.) — N/A, não há mudança incompatível

📚 Checklist de Documentação

  • ✅ Atualizei ou criei documentação OpenAPI em /pages/docs/doc/
  • ✅ Documentação inclui exemplos de requisição e resposta
  • ✅ Documentação está em português
  • ✅ Atualizei README.md se necessário — N/A, rota já listada
  • ✅ N/A - Mudanças não requerem documentação

O exemplo do schema PIX_PARTICIPANTES foi corrigido: mostrava códigos (PDCT/DRCT) que a fonte não retorna desde nov/2025; agora reflete os valores reais (Provedor de Conta Transacional/Direta) e o ISPB com zeros à esquerda.

🧪 Checklist de Testes

  • ✅ Criei ou atualizei testes E2E
  • ✅ Todos os testes passam localmente (npm test)
  • ✅ Teste de CORS funciona corretamente
  • ✅ Testei casos de erro (404, 400, 500, etc.)
  • ✅ Testei casos de sucesso
  • ✅ N/A - Mudanças não requerem testes

O E2E existente (tests/pix-v1.test.js) estava falhando com a fonte morta e volta a passar sem alteração. O teste unitário (tests/services/pix/participants.test.js) foi reescrito: valida o contrato de todos os registros do snapshot (formato do ISPB, campos string/null, ausência de duplicados, mínimo de participantes). O caminho de erro 500 (PIX_LIST_ERROR) foi preservado no service, mas só é alcançável se o snapshot sumir do bundle — as falhas de fonte agora acontecem no CI do snapshot, não em produção.

💻 Checklist de Código

  • ✅ Código segue os padrões do projeto (ESLint + Prettier)
  • ✅ Executei npm run fix antes de commitar
  • ✅ Não adicionei dependências desnecessárias ou pesadas
  • ✅ Código não expõe credenciais ou informações sensíveis
  • ✅ Validei todos os inputs de usuário
  • ✅ Tratei erros apropriadamente
  • ✅ Usei Conventional Commits

pdfjs-dist entra como devDependency de propósito: é usada apenas pelo script de snapshot (local e CI), nunca pelas rotas — não afeta o bundle serverless.

🚀 Checklist de Performance e Custos

  • ✅ Não adicionei processamento pesado que aumenta custos
  • ✅ Usei cache quando apropriado
  • ✅ Minimizei chamadas a APIs externas
  • ✅ Considerei impacto em rate limits de APIs externas
  • ✅ Testei performance em casos de alto volume

A rota fica mais barata e mais rápida: zero chamadas externas em request-time (antes eram até 2 requests ao BCB por cache miss), servindo JSON estático de ~213 KB do bundle com o cache de 6h já existente. O parse de PDF (que estouraria o limite de 10s/256 MB do vercel.json) roda só no CI, 1x por dia útil.

🔍 Como Testar

  1. npm ci && npm run dev, depois curl http://localhost:3000/api/pix/v1/participants — deve retornar 200 com ~880 participantes no contrato atual (compare com produção, que hoje retorna 500)
  2. npm test -- tests/pix-v1.test.js tests/services/pix/participants.test.js — E2E + unitários passam
  3. npm run snapshot:pix:participants — regenera o snapshot a partir do PDF do dia no site do BCB e imprime o resumo das validações (rode em dia útil ou até 1 dia depois; o BCB não publica em fim de semana)

📸 Screenshots (se aplicável)

N/A

📎 Issues Relacionadas

Closes #

📝 Notas Adicionais

Detalhes do parser, para quem for revisar scripts/generate-pix-participants-snapshot.js:

  • O PDF é publicado em paisagem via rotação de página (rotate: 90), então os eixos de transform vêm trocados — o script trata os dois casos.
  • As células da tabela são centralizadas verticalmente: nomes longos quebram em até 5 linhas, acima e abaixo da linha âncora (a que tem o número sequencial). A atribuição de linhas a registros usa a âncora mais próxima, com limite de distância apenas nas bordas da página (onde ficam título/cabeçalho).
  • O PDF contém uma segunda tabela ao final ("participantes em processo de adesão", com outra grade de colunas), que é descartada — o CSV que a rota sempre consumiu continha apenas os ativos.
  • As faixas de colunas são derivadas dos próprios dados (posição do ISPB, valores conhecidos de modalidade/tipo), não de coordenadas fixas, para tolerar pequenas variações de layout.
  • Limitação conhecida (cosmética): quando o BCB quebra a linha de um nome exatamente após um hífen, o join reintroduz um espaço (ex.: LTDA- SICOOB em 1 dos 880 nomes) — indistinguível a partir do PDF. Se o CSV voltar, o fallback corrige isso automaticamente.
  • O parser de CSV portado para o script corrige um bug latente do parser antigo: o CSV do BCB chegou a vir com a linha de cabeçalho repetida no meio do arquivo, e a rota servia essa linha como um participante fantasma ({"ispb": "ISPB", "nome": "Nome Reduzido", ...}). Agora linhas cujo ISPB não seja ^\d{8}$ são descartadas.

felipeflfranca and others added 2 commits August 11, 2026 19:40
O BCB descontinuou o CSV público de participantes do Pix (401 via WAF
para o dia corrente, 404 para anteriores), derrubando a rota com 500 em
produção. A lista oficial agora é publicada apenas em PDF, disponível
somente para os ~2 dias mais recentes.

- scripts/generate-pix-participants-snapshot.js: baixa o PDF (fallback
  de datas), extrai a tabela de participantes ativos com pdfjs-dist
  (devDependency, nunca em request-time) e grava snapshot já no formato
  de resposta da rota, com validações de sanidade
- services/pix/snapshots/latest.json: snapshot versionado (880
  participantes), atualizado em dias úteis via GitHub Action
- rota passa a servir o snapshot; contrato de resposta preservado
  (ispb, nome, nome_reduzido, modalidade_participacao,
  tipo_participacao, inicio_operacao)
- extração validada contra CSV arquivado do formato recente (Wayback,
  10/06/2026): 0 divergências de modalidade nos 879 ISPBs em comum

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O CSV do BCB pode ter sido bloqueado apenas temporariamente (o link
dentro do próprio PDF ainda aponta para ele). O script agora tenta, para
cada data, o CSV original (com colunas resolvidas pelo cabeçalho e
validação que cai para o PDF em caso de formato inesperado) antes de
extrair o PDF. Se o BCB reativar o CSV, a fonte melhor volta a ser usada
automaticamente, sem mudança de código.

O parser de CSV também corrige um bug latente do parser antigo da rota:
linhas de cabeçalho repetidas no meio do arquivo (o BCB já publicou CSV
assim) eram servidas como participante fantasma; agora qualquer linha
cujo ISPB não seja ^\d{8}$ é descartada.

O campo source_format no metadata-latest.json registra qual fonte cada
snapshot usou.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

@felipeflfranca is attempting to deploy a commit to the BrasilAPI Team on Vercel.

A member of the Team first needs to authorize it.

@felipeflfranca felipeflfranca changed the title fix: migra fonte do /pix/v1/participants para snapshot do PDF do BCB fix: migra fonte do /pix/v1/participants para snapshot versionado (BCB descontinuou o CSV) Aug 11, 2026
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
brasilapi Ready Ready Preview Aug 12, 2026 1:33am

Request Review

felipeflfranca and others added 2 commits August 12, 2026 11:11
Apontamentos do SonarQube no PR BrasilAPI#892:
- npm ci --ignore-scripts no workflow (lifecycle scripts não rodam na
  instalação; o job só precisa de axios + pdfjs-dist)
- node:fs / node:path nos requires
- KNOWN_MODALIDADES e KNOWN_TIPOS como Set (.has em vez de .includes)
- replaceAll, optional chaining, Array#includes e Array#at

Aproveitando: o CSV do BCB voltou a responder em 12/08/2026 e o fallback
CSV-primeiro o capturou automaticamente. Isso revelou que o CSV traz uma
segunda tabela concatenada ("Lista de instituições em processo de adesão
ao Pix"), que o parser antigo da rota servia misturada — duplicando
instituições presentes nas duas listas (ex.: WISE). O parseCsv agora
corta na segunda tabela, igual ao parser do PDF, e as duas fontes
produzem exatamente os mesmos 880 participantes.

Snapshot regenerado a partir do CSV: única diferença são nomes
cosmeticamente mais fiéis (espaços duplos preservados, hífen sem espaço
espúrio).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O skip de d2d7856 só sondava o OpenCEP, mas as coordenadas do /cep/v3
vêm do Photon (lib/fetchGeocoordinateFromBrazilLocation), que devolve
latitude/longitude null quando bloqueia o runner — derrubando o CI com
uma falha flaky alheia ao PR. Agora a suíte também pula quando o Photon
está inacessível.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant