diff --git a/docs/blog/posts/2026-02-28-pubsub-event-driven.md b/docs/blog/posts/2026-02-28-pubsub-event-driven.md new file mode 100644 index 0000000..76d42c0 --- /dev/null +++ b/docs/blog/posts/2026-02-28-pubsub-event-driven.md @@ -0,0 +1,185 @@ +--- +date: 2026-02-28 +authors: + - nitai +categories: + - Arquitetura + - Pub/Sub + - Cloud Run + - Pipeline +title: "De DAGs batch para event-driven: a migração Pub/Sub em 48 horas" +hide: + - toc +--- + +# De DAGs batch para event-driven: a migração Pub/Sub em 48 horas + +Dois dias depois de desmembrar o monolito, o pipeline inteiro de processamento de notícias ganhou uma nova arquitetura: event-driven com Pub/Sub. Scrapers publicam eventos, workers processam em cadeia, e o que antes rodava a cada 15 minutos via DAGs agora reage em segundos. Foram **~25 PRs** em **8 repos**, com 3 workers novos, 1 repo novo (`push-notifications`), e a aposentadoria do Cogfy como motor de enriquecimento. + + + +--- + +## Contexto: o gargalo do batch + +No post anterior, contamos como o monolito `data-platform` foi desmembrado em repos especializados. Cada um deployava suas DAGs no Composer, e o Airflow orquestrava tudo com schedules de 10-15 minutos. Funcionava, mas tinha dois problemas: + +1. **Latência**: uma notícia scrapeada às 10:01 só era enriquecida às 10:15, indexada às 10:30 e publicada no Fediverso às 10:45. Quase uma hora de delay. +2. **Acoplamento temporal**: cada DAG precisava "adivinhar" quando a anterior teria terminado. Slots, retries e dependências cross-DAG viravam uma teia frágil. + +A solução estava no plano [`PUBSUB-EVENT-DRIVEN.md`](https://github.com/destaquesgovbr/data-platform/blob/main/_plan/PUBSUB-EVENT-DRIVEN.md): trocar o modelo pull (DAG consulta o banco) por push (evento dispara processamento). + +--- + +## Quinta-feira (27/fev): preparando o terreno + +### AWS Bedrock para LLM + +Antes de montar o pipeline event-driven, precisávamos resolver o enriquecimento via LLM. O Cogfy (serviço externo que usávamos para classificação temática) tinha se tornado um gargalo — lento, sem controle de prompt, e caro. A decisão: migrar para AWS Bedrock com Claude 3 Haiku, chamado diretamente. + +No repo `data-science`, dois PRs construíram a DAG `enrich_news_llm`: +- [`data-science#15`](https://github.com/destaquesgovbr/data-science/pull/15) — DAG + plugin com `NewsClassifier`, taxonomia de temas e job completo (fetch → classify → update PG) +- [`data-science#16`](https://github.com/destaquesgovbr/data-science/pull/16) — fix no prompt para forçar geração do campo `summary` (o Haiku 3 omitia sem instrução explícita) + +Na infra: +- [`infra#89`](https://github.com/destaquesgovbr/infra/pull/89) — Terraform para boto3 no Composer + secret `aws_bedrock` no Secret Manager +- [`infra#90`](https://github.com/destaquesgovbr/infra/pull/90) — refactor: gerenciar a secret version manualmente (credenciais AWS fora do state do Terraform) + +E a limpeza correspondente: +- [`data-platform#92`](https://github.com/destaquesgovbr/data-platform/pull/92) — remove toda a integração com Cogfy do pipeline + +### Ambiente local Airflow + +Em paralelo, três repos ganharam ambiente de desenvolvimento local com Astro CLI, consolidando o padrão que o `activitypub-server` tinha inaugurado na semana anterior: +- [`scraper#6`](https://github.com/destaquesgovbr/scraper/pull/6) +- [`embeddings#5`](https://github.com/destaquesgovbr/embeddings/pull/5) +- [`data-publishing#1`](https://github.com/destaquesgovbr/data-publishing/pull/1) + +### Scraper: refactoring e campo `active` + +- [`scraper#5`](https://github.com/destaquesgovbr/scraper/pull/5) — unificação de patterns dos managers (remove código duplicado) +- [`scraper#4`](https://github.com/destaquesgovbr/scraper/pull/4) (Miguel) — campo `active` no YAML das agências para habilitar/desabilitar sem deletar + +--- + +## Sexta-feira (28/fev): o dia do Pub/Sub + +Este foi o dia mais intenso. Em ~14 horas, a infraestrutura Pub/Sub foi criada, 4 workers foram implementados, e as DAGs batch foram aposentadas. + +### A infraestrutura (manhã) + +Tudo começou com o Terraform. O PR [`infra#91`](https://github.com/destaquesgovbr/infra/pull/91) criou toda a malha: + +``` +Topics Subscriptions → Workers +───────────────────────────────────────────────────────── +dgb.news.scraped ──→ enrichment-worker (data-science) + ──→ typesense-sync (data-platform) +dgb.news.enriched ──→ embeddings-api (embeddings) + ──→ federation-web (activitypub-server) + ──→ push-notifications +dgb.news.embedded (terminal — future use) +``` + +Cada topic tem um DLQ (dead-letter queue) correspondente. Push subscriptions com OIDC authentication. Retry policy com backoff exponencial (10s → 600s). Tudo parametrizado. + +Claro, o primeiro `terraform apply` não passou. Dois fixes rápidos: +- [`infra#92`](https://github.com/destaquesgovbr/infra/pull/92) — SA do GitHub Actions precisava de `pubsub.editor` +- [`infra#93`](https://github.com/destaquesgovbr/infra/pull/93) — upgrade para `pubsub.admin` (faltava `setIamPolicy` para DLQs) + +### Os workers (tarde) + +Com a infraestrutura no ar, os workers foram implementados em paralelo — cada um no seu repo, cada um reutilizando a lógica já existente: + +**Typesense Sync Worker** — [`data-platform#93`](https://github.com/destaquesgovbr/data-platform/pull/93) +Recebe evento de `dgb.news.scraped`, faz upsert no Typesense. Zero duplicação: reutiliza `_build_typesense_query`, `prepare_document` e `get_client` existentes. Dockerfile para Cloud Run. + +**Enrichment Worker** — [`data-science#18`](https://github.com/destaquesgovbr/data-science/pull/18) +Recebe de `dgb.news.scraped`, classifica via Bedrock (temas + summary), atualiza PostgreSQL, publica `dgb.news.enriched`. Reutiliza `NewsClassifier` e `update_news_enrichment`. Processamento idempotente. + +**Embeddings Worker** — [`embeddings#6`](https://github.com/destaquesgovbr/embeddings/pull/6) +Endpoint `POST /process` no `embeddings-api` existente. Recebe de `dgb.news.enriched`, gera embedding com modelo local (sem HTTP hop), atualiza PG, publica `dgb.news.embedded`. + +**Federation Worker** — [`activitypub-server#9`](https://github.com/destaquesgovbr/activitypub-server/pull/9) +Migração completa: a DAG `federation_publish` do Airflow foi substituída por `POST /process` com Pub/Sub push. Busca artigo do banco, insere na fila de publicação com `ON CONFLICT DO NOTHING`. A DAG e o `docker-compose.yml` do Astro CLI foram removidos. + +E um fix na infra que só apareceu em runtime: +- [`infra#94`](https://github.com/destaquesgovbr/infra/pull/94) — subscription de embeddings apontava para service errado (`embeddings_worker` vs `embeddings_api`) + +### O scraper publica eventos + +A peça que conecta tudo: [`scraper#9`](https://github.com/destaquesgovbr/scraper/pull/9). Adicionou `EventPublisher` com graceful degradation — publica em `dgb.news.scraped` após cada INSERT no PostgreSQL. Se a env var `PUBSUB_TOPIC_NEWS_SCRAPED` não existe, é no-op (backward compatible). + +### DAGs aposentadas + +Com os workers no ar, as DAGs batch de enriquecimento, embeddings e federation foram removidas: +- [`data-science#20`](https://github.com/destaquesgovbr/data-science/pull/20) — remove `enrich_news_llm` DAG +- [`embeddings#7`](https://github.com/destaquesgovbr/embeddings/pull/7) — remove embeddings DAG + +### Push Notifications: o novo membro do pipeline + +O evento `dgb.news.enriched` ganhou mais um subscriber: push notifications. Neste dia, a infra foi criada: +- [`infra#95`](https://github.com/destaquesgovbr/infra/pull/95) — subscription Pub/Sub para federation +- [`infra#96`](https://github.com/destaquesgovbr/infra/pull/96) — Cloud Run service `push-notifications` com Pub/Sub, VAPID keys no Secret Manager + +### Documentação + +- [`docs#34`](https://github.com/destaquesgovbr/docs/pull/34) — página de arquitetura dos Pub/Sub workers no docs site + +--- + +## O antes e depois + +### Antes (27/fev): batch com DAGs + +``` +Scraper (15min) → enrich DAG (10min) → embeddings DAG (10min) → federation DAG (10min) + → typesense DAG (10min) +Latência total: ~45-60 minutos +``` + +### Depois (28/fev): event-driven com Pub/Sub + +``` +Scraper INSERT + └→ dgb.news.scraped + ├→ Enrichment Worker (~5s) → dgb.news.enriched + │ ├→ Embeddings Worker (~3s) → dgb.news.embedded + │ ├→ Federation Worker (~2s) + │ └→ Push Notifications (~1s) + └→ Typesense Sync (~2s) + +Latência total: ~10-15 segundos +``` + +--- + +## Números + +| Métrica | Valor | +|---------|-------| +| PRs mergeados | ~25 | +| Repos tocados | 8 (infra, data-platform, data-science, embeddings, scraper, activitypub-server, push-notifications, docs) | +| Workers criados | 4 (enrichment, typesense-sync, embeddings, federation) | +| Topics Pub/Sub | 3 + 4 DLQs | +| DAGs aposentadas | 3 | +| Latência: antes → depois | ~45min → ~15s | +| Dias | 2 | + +--- + +## Lições + +1. **Infraestrutura primeiro, workers depois.** Criar os topics, subscriptions e DLQs no Terraform antes de escrever uma linha de código nos workers permitiu validar a topologia completa. Os workers foram "só" implementar `POST /process`. + +2. **Graceful degradation desbloqueia deploys independentes.** O `EventPublisher` do scraper com no-op quando a env var não existe permitiu mergear o PR do scraper antes da infra Pub/Sub estar no ar. Cada peça deployou no seu ritmo. + +3. **Reutilizar lógica existente é o superpoder de repos bem decompostos.** Cada worker reutilizou funções que já existiam (classificador, prepare_document, get_client). O desmembramento da semana anterior pagou dividendos imediatos. + +4. **DLQs são seguro barato.** Configurar dead-letter queues desde o dia zero custou 10 linhas extras de Terraform e já salvou mensagens perdidas nos primeiros dias. + +5. **De 45 minutos para 15 segundos.** A mudança de arquitetura mais impactante do projeto até agora. Não foi otimização — foi mudança de paradigma. + +--- + +*Próximo post: [Arquitetura Medallion — dados em camadas com Feature Store JSONB](2026-03-01-arquitetura-medallion.md)* diff --git a/docs/blog/posts/2026-03-01-arquitetura-medallion.md b/docs/blog/posts/2026-03-01-arquitetura-medallion.md new file mode 100644 index 0000000..748f859 --- /dev/null +++ b/docs/blog/posts/2026-03-01-arquitetura-medallion.md @@ -0,0 +1,111 @@ +--- +date: 2026-03-01 +authors: + - nitai +categories: + - Arquitetura + - Data Engineering + - BigQuery +title: "Arquitetura Medallion: dados em camadas com Feature Store JSONB" +hide: + - toc +--- + +# Arquitetura Medallion: dados em camadas com Feature Store JSONB + +Em um único dia, formalizamos a arquitetura de dados do DGB com o padrão Medallion (Bronze/Silver/Gold), implementamos as fases 0 a 3, migramos o Composer para `southamerica-east1` e adicionamos sentiment analysis e entity extraction ao enrichment — tudo sem downtime. **12 PRs** em **5 repos**, incluindo o primeiro ADR do projeto. + + + +--- + +## O que é o Medallion e por que agora + +O pipeline do DGB cresceu organicamente: scraper → PostgreSQL → Typesense → Portal. Mas com Pub/Sub (post anterior) disparando eventos em cadeia, os dados passaram a fluir por múltiplos caminhos simultaneamente. Sem uma estrutura clara de camadas, era questão de tempo até confundir dado bruto com dado enriquecido. + +O padrão [Medallion](https://www.databricks.com/glossary/medallion-architecture) organiza os dados em três camadas: + +``` +Bronze (raw) Silver (cleaned) Gold (enriched) +───────────── ────────────── ─────────────── +Dado como veio Normalizado, Classificado, +do scraper deduplicado com features + prontas para consumo +``` + +## ADR-001: a decisão documentada + +[`docs#35`](https://github.com/destaquesgovbr/docs/pull/35) formalizou a decisão como o primeiro Architecture Decision Record do projeto. O ADR define: + +- **Bronze**: dados brutos no GCS (Parquet) + BigQuery external tables +- **Silver**: dados limpos no PostgreSQL (`news` table existente, já normalizada) +- **Gold**: features derivadas em `news_features` (JSONB) — themes, sentiment, entities, embeddings + +A decisão-chave: **Feature Store leve via JSONB no PostgreSQL**, sem introduzir um sistema dedicado. O campo `features JSONB` permite adicionar novas features sem migrations. + +## Implementação: fases 0-3 + +### Fase 0 — Fundação (GCS + BigQuery) + +- Bucket `dgb-data-lake` com lifecycle tiering automático (Standard → Nearline 90d → Coldline 365d) +- BigQuery dataset `bronze` com external tables sobre o GCS +- IAM granular: scraper só escreve, Composer lê + +### Fase 1 — Bronze Ingestion + +- DAG `bronze_news_ingestion`: exporta `news` do PostgreSQL para Parquet no GCS, particionado por data +- DDL BigQuery para views sobre os Parquet files + +### Fase 2 — Feature Store + +- Tabela `news_features` no PostgreSQL com colunas `unique_id`, `features JSONB`, timestamps +- DAGs de backfill para popular features existentes (themes, summary) + +### Fase 3 — Gold Analytics + +- DAG `sync_analytics_to_bigquery`: pageviews e engagement do Umami para BigQuery +- DAG `export_gold_features`: features agregadas para análise no BigQuery + +### PRs coordenados + +| PR | Repo | Escopo | +|----|------|--------| +| [`docs#35`](https://github.com/destaquesgovbr/docs/pull/35) | docs | ADR-001 | +| [`data-platform#98`](https://github.com/destaquesgovbr/data-platform/pull/98) | data-platform | Fases 0-3 completas (DAGs, jobs, SQL, testes) | +| [`infra#98`](https://github.com/destaquesgovbr/infra/pull/98) | infra | Terraform: GCS bucket, BigQuery dataset, IAM | +| [`data-science#22`](https://github.com/destaquesgovbr/data-science/pull/22) | data-science | Enrichment: sentiment + entities para Feature Store | +| [`data-platform#99`](https://github.com/destaquesgovbr/data-platform/pull/99) | data-platform | Fix SQL BigQuery + deploy de DAGs | +| [`data-platform#100`](https://github.com/destaquesgovbr/data-platform/pull/100) | data-platform | Fix dialeto SQLAlchemy + tipos Parquet | + +## Migração do Composer para southamerica-east1 + +Aproveitando a janela de mudanças, migramos o Cloud Composer de `us-central1` para `southamerica-east1` — consolidando todos os recursos GCP na mesma região: + +- [`infra#97`](https://github.com/destaquesgovbr/infra/pull/97) — Terraform: novo Composer na região correta +- [`reusable-workflows#4`](https://github.com/destaquesgovbr/reusable-workflows/pull/4) — atualiza região no workflow compartilhado +- [`data-platform#97`](https://github.com/destaquesgovbr/data-platform/pull/97) — docs atualizados +- [`infra#99`](https://github.com/destaquesgovbr/infra/pull/99), [`infra#100`](https://github.com/destaquesgovbr/infra/pull/100) — fixes de permissões IAM + +## Enrichment expandido: sentiment + entities + +[`data-science#22`](https://github.com/destaquesgovbr/data-science/pull/22) estendeu o enrichment worker para extrair, do **mesmo prompt Bedrock** (custo marginal zero): + +- **Sentiment**: positivo/negativo/neutro com score de confiança +- **Entities**: pessoas, organizações, locais mencionados na notícia + +Os resultados vão para o Feature Store (`news_features.features` JSONB), prontos para consumo pelo portal e por análises no BigQuery. + +## Números + +| Métrica | Valor | +|---------|-------| +| PRs mergeados | 12 | +| Repos tocados | 5 | +| DAGs novas | 4 (bronze ingestion, sync analytics, export gold, backfill) | +| Tabelas criadas | 2 (news_features, BigQuery external) | +| Custo incremental estimado | +$2-4/mês | +| Downtime | 0 | + +--- + +*Próximo post: [Analytics, Auth e Portal v1.0.0](2026-03-03-portal-v1.md)* diff --git a/docs/blog/posts/2026-03-03-portal-v1.md b/docs/blog/posts/2026-03-03-portal-v1.md new file mode 100644 index 0000000..8dc3cc5 --- /dev/null +++ b/docs/blog/posts/2026-03-03-portal-v1.md @@ -0,0 +1,202 @@ +--- +date: 2026-03-03 +authors: + - nitai +categories: + - Portal + - Infraestrutura + - Analytics + - Release +title: "Umami, GrowthBook, Auth e v1.0.0: dois dias até o release do Portal" +hide: + - toc +--- + +# Umami, GrowthBook, Auth e v1.0.0: dois dias até o release do Portal + +Sábado de manhã o portal não tinha analytics, nem autenticação, nem feature flags. Domingo à noite, tínhamos Umami Analytics com IAP, GrowthBook com dual-service, autenticação Google OAuth, push notifications com service worker, e a tag `v1.0.0` no ar. Foram **~25 PRs** em **6 repos**, com 3 serviços novos no Cloud Run, e uma release que consolida semanas de trabalho. + + + +--- + +## Contexto: o que faltava para o v1.0.0 + +O portal já tinha busca semântica, artigos, feeds RSS e o pipeline de dados rodando event-driven. Mas para uma release pública, três pilares estavam ausentes: + +1. **Analytics**: sem dados de uso, estávamos voando às cegas +2. **Experimentação**: sem feature flags, qualquer mudança era tudo-ou-nada +3. **Autenticação**: sem login, funcionalidades personalizadas (como notificações push) ficavam limitadas + +O plano: resolver os três em um fim de semana. + +--- + +## Sábado (2/mar): a plataforma de analytics e experimentação + +### Umami Analytics no Cloud Run + +Primeira escolha: Umami Analytics como alternativa privacy-first ao Google Analytics. Open source, self-hosted, sem cookies. + +[`infra#101`](https://github.com/destaquesgovbr/infra/pull/101) provisionou tudo de uma vez: +- Cloud Run service com a imagem oficial do Umami +- Database `umami` + user `umami_app` no Cloud SQL existente +- Secrets auto-gerados no Secret Manager +- Scale-to-zero, health checks, service account dedicado + +O primeiro deploy não subiu. Começou o ciclo clássico de ajustes: +- [`infra#102`](https://github.com/destaquesgovbr/infra/pull/102) — imagem do ghcr.io precisava de mirror; GrowthBook falhava com startup probes +- [`infra#103`](https://github.com/destaquesgovbr/infra/pull/103) — remove startup probes do Umami (primeiro deploy sem dados = liveness check falha) +- [`infra#104`](https://github.com/destaquesgovbr/infra/pull/104) — `DATABASE_URL` faltava `localhost` (Cloud SQL Auth Proxy escuta em localhost, não no Unix socket) + +### GrowthBook: dual-service architecture + +GrowthBook foi mais complexo. Uma única instância serve tanto o frontend (dashboard) quanto a API (SDK endpoints para o portal). O problema: o frontend precisa ser protegido (acesso interno), mas a API precisa ser pública (portal consome SDK em runtime). + +A solução em [`infra#105`](https://github.com/destaquesgovbr/infra/pull/105): **dois Cloud Run services** a partir da mesma imagem: +- `growthbook` (porta 3000): container completo com PM2 — frontend Next.js + API interno +- `growthbook-api` (porta 3100): Express backend only — SDK endpoints públicos + +Secrets separados (`encryption-key`, `jwt-secret`), CORS configurado, MongoDB Atlas como backend. + +### IAP: protegendo dashboards com Identity-Aware Proxy + +Com Umami e GrowthBook no ar, qualquer um com a URL poderia acessar os dashboards. A solução: IAP (Identity-Aware Proxy) do GCP com HTTPS Load Balancer compartilhado. + +[`infra#107`](https://github.com/destaquesgovbr/infra/pull/107) criou: +- IP estático + certificado SSL gerenciado +- Backend services com IAP habilitado +- Umami com `DISABLE_LOGIN=1` (IAP resolve auth, sem login duplo) +- GrowthBook frontend com ingress restrito ao LB +- GrowthBook API continua pública (sem IAP — SDK do portal precisa acessar) +- Domínios `nip.io` derivados automaticamente do IP estático + +Dois fixes depois ([`infra#108`](https://github.com/destaquesgovbr/infra/pull/108) — adicionar email ao IAP, [`infra#110`](https://github.com/destaquesgovbr/infra/pull/110) — scheme `EXTERNAL_MANAGED` para o LB), os dashboards estavam protegidos. + +### Ingestão Umami → BigQuery + +Com o Umami coletando dados, o próximo passo: trazer esses dados para o data warehouse. + +[`data-platform#101`](https://github.com/destaquesgovbr/data-platform/pull/101) criou a DAG `sync_umami_to_bigquery`: +- Sync incremental de pageviews e custom events +- Queries validadas contra o banco de produção do Umami +- 28 testes unitários novos +- [`data-platform#102`](https://github.com/destaquesgovbr/data-platform/pull/102) — refactor removendo pandas/pyarrow (desnecessários) + +Infra companion: [`infra#109`](https://github.com/destaquesgovbr/infra/pull/109) — connection `umami_postgres` via Secret Manager no Composer. + +### Documentação operacional + +- [`docs#36`](https://github.com/destaquesgovbr/docs/pull/36) — páginas de referência Umami + GrowthBook no docs site +- [`infra#106`](https://github.com/destaquesgovbr/infra/pull/106) — runbooks operacionais + +--- + +## Domingo (3/mar): autenticação, push e release + +### Autenticação Google OAuth + +Lucas implementou autenticação com NextAuth.js v5 (Auth.js): + +[`portal#81`](https://github.com/destaquesgovbr/portal/pull/81) — provider condicional: +- **Google OAuth** para dev e preview +- **Gov.Br OIDC** preparado para produção (pendente aprovação SGD + domínio .gov.br) +- Botão de login aparece apenas quando variáveis estão configuradas + +Security hardening: +- [`portal#88`](https://github.com/destaquesgovbr/portal/pull/88) — migra `AUTH_SECRET` de env var plain text para Secret Manager +- [`portal#87`](https://github.com/destaquesgovbr/portal/pull/87) — remove fetch extra no `AuthButton` (perf) + +### Push Notifications: bell icon + Service Worker + +[`portal#82`](https://github.com/destaquesgovbr/portal/pull/82) trouxe a experiência completa de push notifications ao portal: + +- **Service Worker** (`sw.js`): recebe push events, exibe notificações nativas do browser +- **ServiceWorkerRegistrar**: registra o SW no mount, incluído no layout root +- **PushSubscriber**: ícone de sino no header com Sheet lateral + - 25 checkboxes de temas L1 + - Subscribe/unsubscribe via Web Push API (VAPID) + - Persistência de filtros no localStorage +- Integração com o backend `push-notifications` via Pub/Sub + +### Fixes e melhorias + +- [`portal#84`](https://github.com/destaquesgovbr/portal/pull/84) — amplia `remotePatterns` no Next.js para corrigir imagens quebradas nos cards (closes [#86](https://github.com/destaquesgovbr/portal/issues/86)) +- [`scraper#10`](https://github.com/destaquesgovbr/scraper/pull/10) — distribui ~155 DAGs uniformemente em slots de 10min (antes: 156 simultâneas) +- [`scraper#11`](https://github.com/destaquesgovbr/scraper/pull/11) — API retorna HTTP 500/207 para falhas (antes: sempre 200) +- [`infra#114`](https://github.com/destaquesgovbr/infra/pull/114), [`infra#115`](https://github.com/destaquesgovbr/infra/pull/115), [`reusable-terraform#3`](https://github.com/destaquesgovbr/reusable-terraform/pull/3), [`reusable-terraform#4`](https://github.com/destaquesgovbr/reusable-terraform/pull/4) — ignore scaling drift no Cloud Run (Terraform ficava querendo reverter autoscaling) + +### Streamlit: panorama-dgb + +[`infra#113`](https://github.com/destaquesgovbr/infra/pull/113) — novo app Streamlit `panorama-dgb` registrado na plataforma. Dashboard interno para visualização do estado do projeto. + +### Release v1.0.0 + +[`portal#89`](https://github.com/destaquesgovbr/portal/pull/89) — **Release v1.0.0** consolida tudo: + +- Notificações Push (SW + bell icon + subscriber UI) +- Autenticação Google OAuth via NextAuth.js +- Widget de leitura recente (Firestore) +- Feeds RSS/Atom/JSON +- Fix de imagens quebradas +- Migração de AUTH_SECRET para Secret Manager +- Otimização de performance no AuthButton + +--- + +## O antes e depois + +### Portal na sexta (28/fev) + +``` +Portal Next.js +├── Busca semântica (Typesense) +├── Artigos com paginação +├── Feeds RSS/Atom/JSON +└── (sem analytics, sem auth, sem push) +``` + +### Portal no domingo (2/mar) + +``` +Portal Next.js v1.0.0 +├── Busca semântica (Typesense) +├── Artigos com paginação +├── Feeds RSS/Atom/JSON +├── Google OAuth (NextAuth.js) +├── Push Notifications (VAPID + SW) +├── Umami Analytics (self-hosted, privacy-first) +├── GrowthBook Feature Flags (dual-service) +└── IAP protegendo dashboards +``` + +--- + +## Números + +| Métrica | Valor | +|---------|-------| +| PRs mergeados | ~25 | +| Repos tocados | 6 (portal, infra, data-platform, docs, scraper, reusable-terraform) | +| Serviços Cloud Run novos | 3 (Umami, GrowthBook, GrowthBook API) | +| Testes novos | 35+ | +| Release tag | v1.0.0 | +| Dias | 2 (sáb-dom) | + +--- + +## Lições + +1. **IAP > login duplo.** Proteger dashboards com Identity-Aware Proxy e desabilitar o login nativo da ferramenta elimina fricção. Um único ponto de autenticação (Google Workspace) para todos os dashboards internos. + +2. **Dual-service resolve o dilema público/privado.** Quando um serviço precisa ser parcialmente público (API) e parcialmente privado (dashboard), dois Cloud Run services da mesma imagem com configs diferentes é mais limpo que network policies complexas. + +3. **Feature flags desde o dia zero.** GrowthBook estava no ar antes de precisarmos de um A/B test. Quando o primeiro teste for necessário, a infraestrutura já existe. + +4. **Scale-to-zero é amigo de serviços auxiliares.** Umami e GrowthBook provavelmente ficam idle 95% do tempo. Com scale-to-zero, o custo é praticamente zero quando não há acesso. + +5. **Release as consolidation, not event.** O v1.0.0 não foi um big bang — foi uma tag num commit que já estava rodando em produção há horas. Cada feature foi deployada e validada individualmente via PRs. A release apenas formalizou. + +--- + +*Próximo post: [Bot Telegram e scraper resiliente](2026-03-04-telegram-bot-scraper.md)* diff --git a/docs/blog/posts/2026-03-04-telegram-bot-scraper.md b/docs/blog/posts/2026-03-04-telegram-bot-scraper.md new file mode 100644 index 0000000..e383ad1 --- /dev/null +++ b/docs/blog/posts/2026-03-04-telegram-bot-scraper.md @@ -0,0 +1,147 @@ +--- +date: 2026-03-04 +authors: + - nitai +categories: + - Telegram + - Scraper + - Push Notifications +title: "Bot Telegram e scraper resiliente: push notifications v2 e otimização de coleta" +hide: + - toc +--- + +# Bot Telegram e scraper resiliente: push notifications v2 e otimização de coleta + +O pipeline event-driven disparava notificações web push, mas faltava o canal mais direto: Telegram. Em paralelo, o scraper ganhou otimizações que cortaram requests HTTP pela metade e corrigiram agências com falha persistente. **~12 PRs** em **5 repos**, incluindo 2 repos novos (`push-notifications`, `telegram-bot`) e infraestrutura completa no Cloud Run. + + + +--- + +## Push Notifications v2 + +O serviço `push-notifications` já existia desde o post do Pub/Sub, mas a v1 era mínima: recebia evento, mandava web push para todos os subscribers. A v2 trouxe personalização real. + +### Filtros expandidos + auth + +[`push-notifications#1`](https://github.com/destaquesgovbr/push-notifications/pull/1): + +- **Filtros granulares**: themes L2/L3, tags e keywords (v1 só tinha L1) +- **Cooldown de 15min** entre notificações para o mesmo subscriber (anti-spam) +- **`user_id` opcional** para vincular subscriptions a usuários autenticados +- **Migração de dados**: converte theme labels existentes para codes + +### Notificações Telegram com formato rico + +[`push-notifications#2`](https://github.com/destaquesgovbr/push-notifications/pull/2): + +- `telegram_sender.py` com envio de foto (`sendPhoto`) + fallback para texto (`sendMessage`) +- Formato rico: agência, categoria, título, resumo, data e link +- Handler despacha por canal: webpush vs telegram +- Migração: colunas `channel`, `chat_id`, `username` em `push_subscriptions` + +--- + +## Bot Telegram: @destaquesgovbr_bot + +O bot permite que usuários se inscrevam em agências, temas e keywords via menus inline — zero digitação exceto para busca. + +### Arquitetura + +``` +Telegram API + └→ Webhook POST /telegram/webhook + └→ bot.py: handle_update() + ├→ /start → menu principal + ├→ Seguir Agências → keyboard com TOP_AGENCIES + busca + ├→ Seguir Temas → keyboard paginado (8/página, L1→L2) + └→ Keywords → entrada de texto livre +``` + +O bot compartilha o banco `govbrnews` (tabela `push_subscriptions`) com o serviço `push-notifications`. Comunicação é via shared DB — sem chamadas diretas entre serviços. + +### Infra Cloud Run + +[`infra#116`](https://github.com/destaquesgovbr/infra/pull/116) criou toda a infraestrutura: + +| Recurso | Detalhe | +|---------|---------| +| Service Account | `destaquesgovbr-tg-bot` | +| Artifact Registry | `destaquesgovbr-telegram-bot` (cleanup 30d) | +| Secret Manager | `telegram-bot-token`, `telegram-webhook-secret` | +| Cloud Run | Webhook mode, scale-to-zero | +| WIF binding | Deploy via GitHub Actions | + +--- + +## Scraper resiliente + +### Known URL fence + +[`scraper#12`](https://github.com/destaquesgovbr/scraper/pull/12) — a otimização mais impactante do scraper: + +1. Antes de scrapear uma agência, consulta URLs recentes no PostgreSQL +2. Artigos com URL já conhecida: **skip sem fetch** (economia de HTTP request) +3. **3 artigos consecutivos conhecidos = parada antecipada** ("fence") + +Na prática: se as 3 primeiras notícias da listagem já são conhecidas, o scraper para de processar a página. Para a maioria das agências, isso significa scrapear 3-5 artigos em vez de 20-30. Redução de ~60% nos HTTP requests. + +Backward-compatible: sem `known_urls`, comportamento idêntico ao anterior. 11 testes unitários cobrem os cenários. + +### URLs de agências com falha persistente + +[`scraper#14`](https://github.com/destaquesgovbr/scraper/pull/14) — corrigiu 4 das 12 agências com falha persistente: + +| Agência | Problema | Correção | +|---------|----------|----------| +| `ctir` | URL com `/2025` hardcoded | Removido sufixo de ano | +| `sri` | URL profunda com path longo | Simplificado para `/noticias` | +| `hfa` | `?b_start:int=0` duplicado pelo scraper | Removido query param | +| (4a) | URL inacessível | Corrigida | + +### Referências HuggingFace em logs + +[`scraper#8`](https://github.com/destaquesgovbr/scraper/pull/8) (Mauricio) — cleanup de referências ao HuggingFace nos logs do scraper (legado da época em que o scraper fazia upload direto). + +--- + +## Números + +| Métrica | Valor | +|---------|-------| +| PRs mergeados | ~12 | +| Repos tocados | 5 (push-notifications, infra, scraper, telegram-bot, portal) | +| Repos novos no pipeline | 2 (push-notifications, telegram-bot) | +| HTTP requests economizados | ~60% (known URL fence) | +| Agências corrigidas | 4 | + +--- + +## Situação atual do pipeline + +Após esta semana de posts (26/fev → 4/mar), o pipeline completo é: + +``` +~155 Agências Gov.BR + └→ Scraper (Cloud Run, 10min, known URL fence) + └→ PostgreSQL INSERT + Pub/Sub event + ├→ Enrichment Worker (Bedrock → themes, summary, sentiment, entities) + │ └→ dgb.news.enriched + │ ├→ Embeddings Worker (local model → vector) + │ ├→ Federation Worker (ActivityPub → Fediverso) + │ ├→ Push Notifications (web push + Telegram) + │ └→ Typesense Sync (busca semântica) + └→ Bronze Ingestion (GCS Parquet → BigQuery) + +Portal v1.0.0 (Next.js) +├── Busca semântica (Typesense) +├── Google OAuth (NextAuth.js) +├── Push Notifications (VAPID + Service Worker) +├── Bot Telegram (@destaquesgovbr_bot) +├── Umami Analytics (IAP-protected) +├── GrowthBook Feature Flags +└── Feeds RSS/Atom/JSON +``` + +De monolito batch a pipeline event-driven com 8+ serviços — em 7 dias, ~75 PRs, 11 repos.