Skip to content

fix(votes): popular vote_date a partir da API em vez do JSON quebrado#89

Merged
luizvi merged 1 commit into
mainfrom
fix/vote-date-column
Jun 2, 2026
Merged

fix(votes): popular vote_date a partir da API em vez do JSON quebrado#89
luizvi merged 1 commit into
mainfrom
fix/vote-date-column

Conversation

@luizvi

@luizvi luizvi commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Problema

Cards de votações na dashboard exibiam datas erradas (ex: "23/05/2026" pra votações de 2020). Causa: o endpoint /roll-call-votes/ extraía date_vote de proposition.details.decisao_destino.Decisao.Data — estrutura JSON que só existe em ~3% dos votos do Senado e nunca para Câmara — então a UI caía pro created_at (Timeline.tsx:128: v.date_vote?.slice(0, 10) ?? v.created_at?.slice(0, 10)), exibindo o dia da ingestão.

Cobertura medida em prod (Jun/2026):

  • Câmara: 0 de 221.217 votos com data extraível
  • Senado: 2.157 de 70.110 (~3%)
  • Total: 0,74% dos votos com data correta

Solução

Trazer a fonte de verdade pra uma coluna dedicada (vote_date DATE), populada pelo crawler na ingestão.

Mudanças

  1. Migration 7c91a3e2f4d8_add_vote_date_to_roll_call_votes.py: adiciona vote_date DATE NULL + índice em roll_call_votes
  2. Models (api/db/models/roll_call_votes.py e mamute_scrappers/db/models/roll_call_votes.py): novo campo
  3. Crawler Câmara: já parseava dataHoraRegistro no payload (linha 349 antes do PR), mas o upsert não persistia. Agora persiste em _upsert_roll_call_vote
  4. Crawler Senado: análogo — dataSessao já no payload, agora persistido via .date()
  5. Endpoint api/routers/roll_call_votes.py: prefere vote.vote_date; mantém fallback (_extract_date_vote_legacy) só pros votos pré-PR aguardando backfill. Removeremos o fallback em PR posterior, quando cobertura chegar a 100%
  6. Script novo mamute_scrappers/scripts/backfill_vote_dates.py: orquestrador do backfill histórico. Agrupa votos pendentes por link (cada link representa 1 votação = N votos, então ~5-10k chamadas HTTP cobrem todos os 291k votos), faz GET por votação, UPDATE em lote. Padrão idêntico aos outros backfills (state file, flock, --chunks-per-run, --status, auto-encerra quando termina)
  7. Cron (mamute_scrappers/docker/scrappers.cron): entry horária :55 + @reboot burst — resiliente a restart de container (mesma estratégia da PR feat(scrappers): bursts de backfill resilientes a restart de container #88)

Testes

  • api/tests/test_roll_call_votes_date.py: endpoint usa coluna quando populada; cai no fallback quando NULL; helpers do extrator legado
  • mamute_scrappers/tests/test_roll_call_votes_vote_date_parse.py: parse dos payloads (Câmara dataHoraRegistro, Senado dataSessao com variações de capitalização)
  • mamute_scrappers/tests/test_backfill_vote_dates.py: helpers puros do orquestrador (_parse_date, _scan_for_date, _resolve_fetcher)

Resultado local: pytest api/tests/ -v → 27 passed (todos verdes, incluindo o test_project_dashboard_activity que precisou de update do schema inline).

Plano de deploy

  1. Merge → CI roda alembic upgrade head automaticamente (PR ci(deploy-prd): aplica alembic upgrade head em prod apos rebuild #79 já cobre isso)
  2. Após deploy, crawlers incrementais começam a popular vote_date nas próximas execuções (rolam de 3 em 3 horas)
  3. @reboot burst do backfill_vote_dates dispara assim que o container reinicia (~30s pós-boot); o cron :55 mantém ritmo horário caso o burst pare antes de zerar
  4. Conforme vote_date é populado, o fallback legado vai sendo dispensado naturalmente. Quando cobertura ≈ 100%, abre-se PR de cleanup removendo _extract_date_vote_legacy

Test plan

  • Após merge: confirmar que alembic upgrade head rodou no deploy (esperar a coluna no DB)
  • Verificar que crawler camara-roll-call-votes no próximo run popula vote_date em novos votos
  • Idem para senado-roll-call-votes
  • Conferir que backfill_vote_dates --status retorna progresso plausível pós-deploy
  • Olhar a dashboard: cards de votação antiga devem usar vote_date correto (não mais o created_at)
  • Depois de algumas horas: re-medir cobertura (SELECT COUNT(*) FILTER (WHERE vote_date IS NOT NULL) * 100.0 / COUNT(*) FROM roll_call_votes)

🤖 Generated with Claude Code

Cards de votações na dashboard exibiam data errada (ex: "23/05/2026" pra
votações de 2020). O endpoint /roll-call-votes/ extraía date_vote de
proposition.details.decisao_destino.Decisao.Data, estrutura que só existe
em ~3% dos votos do Senado e nunca para Câmara — então a UI caía pro
created_at (dia da ingestão).

Cobertura medida: 0 de 221.217 votos da Câmara tinham data extraível;
apenas 2.157 de 70.110 do Senado (3%). Total: 0.74% dos votos com data
correta.

Solução:
- Migration adiciona coluna roll_call_votes.vote_date (DATE, nullable,
  com índice) — em ambos os models (api/ e mamute_scrappers/)
- Crawler Câmara já parseava dataHoraRegistro no payload (linha 349) mas
  não persistia: agora _upsert_roll_call_vote grava vote_date
- Crawler Senado análogo: parseava dataSessao no payload (linha 242), agora
  persiste via session_dt.date()
- Endpoint usa vote.vote_date direto; mantém fallback temporário pro
  extrator legado pros votos pré-PR ainda sem vote_date populado
- Script orquestrador backfill_vote_dates: agrupa votos pendentes por link
  (cada link = 1 votação = N votos), faz uma chamada HTTP por votação e
  UPDATE em lote. Padrão idêntico aos outros backfills: state file, flock,
  --chunks-per-run, --status, auto-encerra quando esvazia
- Cron horário (:55, fora de conflito com os outros) + @reboot burst, para
  resilência a restart de container (mesma estratégia da PR #88)
- Tests: endpoint (column vs JSON fallback), parse dos crawlers
  (dataHoraRegistro/dataSessao no payload), helpers do orquestrador
  (parse_date, scan_for_date, dispatch por host)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@luizvi luizvi merged commit 9f12260 into main Jun 2, 2026
7 checks passed
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