From 896d3e831b262df1e3324d5a29a602819613fa6b Mon Sep 17 00:00:00 2001 From: nitai Date: Tue, 13 Jan 2026 09:59:20 -0300 Subject: [PATCH 1/3] docs: atualiza arquitetura para PostgreSQL como fonte de verdade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona docs/arquitetura/postgresql.md com schema detalhado - Adiciona docs/modulos/data-platform.md documentando novo repo - Adiciona docs/workflows/airflow-dags.md com DAGs do Composer - Atualiza diagramas em visao-geral.md e fluxo-de-dados.md - Atualiza index.md com nova arquitetura e repositórios - Atualiza componentes-estruturantes.md (HF agora é distribuição) - Atualiza mkdocs.yml com novos arquivos na navegação Mudança principal: PostgreSQL (Cloud SQL) é agora a fonte de verdade, HuggingFace passa a ser camada de distribuição de dados abertos. Co-Authored-By: Claude Opus 4.5 --- _plan/atualizacao-arquitetura-postgresql.md | 246 ++++++++++++++ docs/arquitetura/componentes-estruturantes.md | 103 ++++-- docs/arquitetura/fluxo-de-dados.md | 163 ++++++--- docs/arquitetura/postgresql.md | 308 +++++++++++++++++ docs/arquitetura/visao-geral.md | 167 +++++++--- docs/index.md | 20 +- docs/modulos/data-platform.md | 274 +++++++++++++++ docs/workflows/airflow-dags.md | 315 ++++++++++++++++++ mkdocs.yml | 3 + 9 files changed, 1461 insertions(+), 138 deletions(-) create mode 100644 _plan/atualizacao-arquitetura-postgresql.md create mode 100644 docs/arquitetura/postgresql.md create mode 100644 docs/modulos/data-platform.md create mode 100644 docs/workflows/airflow-dags.md diff --git a/_plan/atualizacao-arquitetura-postgresql.md b/_plan/atualizacao-arquitetura-postgresql.md new file mode 100644 index 0000000..be24c2f --- /dev/null +++ b/_plan/atualizacao-arquitetura-postgresql.md @@ -0,0 +1,246 @@ +# Plano de Atualização da Documentação - DestaquesGovBr + +## Contexto da Mudança Arquitetural + +### Antes (Documentação Atual) +- **Source of Truth**: HuggingFace Dataset +- **Repositórios**: `scraper`, `typesense` (separados) +- **Fluxo**: Sites → Scraper → HuggingFace → Cogfy → HuggingFace → Typesense + +### Agora (Nova Arquitetura) +- **Source of Truth**: PostgreSQL (Cloud SQL) +- **Repositórios**: `data-platform` (unificado) - repos antigos arquivados +- **Fluxo**: Sites → Scraper → PostgreSQL (+HF dual-write) → Cogfy → PostgreSQL → Typesense / HuggingFace (sync) +- **Novos componentes**: Embeddings API, Cloud Composer (Airflow), DAGs + +### Decisões do Usuário +- [x] Criar novos arquivos + atualizar existentes +- [x] Remover todas referências aos repos arquivados (scraper, typesense) +- [x] Atualizar completamente as trilhas de onboarding + +--- + +## Arquivos a Criar (Novos) + +| Arquivo | Conteúdo | +|---------|----------| +| `docs/modulos/data-platform.md` | Visão geral do repo unificado, estrutura, CLI | +| `docs/arquitetura/postgresql.md` | Schema detalhado (5 tabelas), índices, normalização | +| `docs/workflows/airflow-dags.md` | DAGs do Cloud Composer (sync HF, etc) | + +--- + +## Arquivos a Modificar + +### 1. Arquitetura (Alta Prioridade) + +| Arquivo | Mudanças | +|---------|----------| +| `docs/index.md` | Novo diagrama com PostgreSQL central, atualizar descrição | +| `docs/arquitetura/visao-geral.md` | Redesenhar diagramas Mermaid, PostgreSQL como camada 3 | +| `docs/arquitetura/fluxo-de-dados.md` | Reescrever 8 etapas (incluindo embeddings e sync HF) | +| `docs/arquitetura/componentes-estruturantes.md` | HuggingFace → "distribuição", nova seção PostgreSQL | + +### 2. Módulos (Alta Prioridade) + +| Arquivo | Mudanças | +|---------|----------| +| `docs/modulos/scraper.md` | Repo → data-platform, CLI, StorageAdapter, PostgresManager | +| `docs/modulos/typesense-local.md` | Repo → data-platform, lê do PostgreSQL, embeddings | +| `docs/modulos/cogfy-integracao.md` | Enrichment escreve no PostgreSQL | + +### 3. Workflows (Média Prioridade) + +| Arquivo | Mudanças | +|---------|----------| +| `docs/workflows/scraper-pipeline.md` | Novo repo, dual-write PostgreSQL | +| `docs/workflows/typesense-data.md` | Sync lê do PostgreSQL, inclui embeddings | + +### 4. Infraestrutura (Média Prioridade) + +| Arquivo | Mudanças | +|---------|----------| +| `docs/infraestrutura/arquitetura-gcp.md` | Adicionar Cloud SQL, Composer, Embeddings API, custos | + +### 5. Onboarding (23 arquivos) + +| Arquivo | Mudanças Necessárias | +|---------|---------------------| +| `setup-backend.md` | Atualizar refs ao data-platform | +| `setup-datascience.md` | Atualizar conexão PostgreSQL | +| `setup-devvm.md` | Verificar refs a repos | +| `airflow-tutorial.md` | Verificar se alinhado com Composer 3 | +| `roteiro-onboarding.md` | Atualizar links e estrutura | +| `troubleshooting.md` | Adicionar PostgreSQL | +| `ds/explorando-dataset/*.md` | Atualizar fonte de dados (PostgreSQL vs HF) | +| `ds/nlp-pipeline/*.md` | Verificar embeddings e busca semântica | +| `ds/ml-classificacao/*.md` | Verificar pipeline | +| `ds/qualidade-dados/*.md` | Verificar pipeline | + +--- + +## Detalhamento dos Novos Arquivos + +### `docs/modulos/data-platform.md` + +```markdown +# Data Platform + +Repositório unificado para toda a infraestrutura de dados do DestaquesGovBr. + +## Componentes Principais +- **Scraper**: Coleta de notícias (gov.br + EBC) +- **Enrichment**: Integração com Cogfy (temas + resumos) +- **Embeddings**: Geração de vetores 768-dim +- **Typesense Sync**: Indexação para busca +- **HuggingFace Sync**: Distribuição de dados abertos + +## CLI +- `data-platform scrape` +- `data-platform enrich` +- `data-platform sync-typesense` +- `data-platform generate-embeddings` + +## Storage Adapter +Modos: POSTGRES, HUGGINGFACE, DUAL_WRITE +``` + +### `docs/arquitetura/postgresql.md` + +```markdown +# PostgreSQL - Fonte de Verdade + +## Schema (5 tabelas) +- agencies (158 registros) +- themes (200+ registros, 3 níveis) +- news (300k+ registros) +- sync_log (auditoria) + +## Configuração Cloud SQL +- PostgreSQL 15 +- 1 vCPU, 3.75GB RAM +- 50GB SSD (auto-resize até 500GB) +- Backups diários, PITR 7 dias +``` + +### `docs/workflows/airflow-dags.md` + +```markdown +# DAGs do Cloud Composer + +## sync_postgres_to_huggingface +- Execução diária +- Incremental via parquet shards +- Consulta IDs existentes via Dataset Viewer API +``` + +--- + +## Mudanças Detalhadas nos Diagramas + +### Diagrama Principal (`index.md`) + +**Atual:** +``` +160+ Sites gov.br → Scraper → Cogfy/LLM → HuggingFace → Typesense → Portal +``` + +**Novo:** +```mermaid +graph LR + A[160+ Sites gov.br] --> B[Scraper] + B --> C[(PostgreSQL)] + C --> D[Cogfy/LLM] + D --> C + C --> E[Embeddings API] + E --> C + C --> F[Typesense] + F --> G[Portal Next.js] + C --> H{DAG Airflow} + H --> I[(HuggingFace)] +``` + +### Diagrama de Arquitetura (`visao-geral.md`) + +**Camadas atualizadas:** +1. **Coleta**: Scraper (data-platform) +2. **Enriquecimento**: Cogfy LLM +3. **Armazenamento**: PostgreSQL (fonte de verdade) +4. **Processamento**: Embeddings API +5. **Indexação**: Typesense (com vectors) +6. **Distribuição**: HuggingFace (dados abertos) +7. **Apresentação**: Portal Next.js + +--- + +## Ordem de Execução + +### Fase 1 - Novos Arquivos +1. Criar `docs/modulos/data-platform.md` +2. Criar `docs/arquitetura/postgresql.md` +3. Criar `docs/workflows/airflow-dags.md` + +### Fase 2 - Arquitetura Core +1. `docs/index.md` +2. `docs/arquitetura/visao-geral.md` +3. `docs/arquitetura/fluxo-de-dados.md` +4. `docs/arquitetura/componentes-estruturantes.md` + +### Fase 3 - Módulos +1. `docs/modulos/scraper.md` +2. `docs/modulos/typesense-local.md` +3. `docs/modulos/cogfy-integracao.md` + +### Fase 4 - Workflows e Infra +1. `docs/workflows/scraper-pipeline.md` +2. `docs/workflows/typesense-data.md` +3. `docs/infraestrutura/arquitetura-gcp.md` + +### Fase 5 - Onboarding +1. `docs/onboarding/roteiro-onboarding.md` +2. `docs/onboarding/setup-backend.md` +3. `docs/onboarding/setup-datascience.md` +4. Trilha DS (explorando-dataset, nlp-pipeline, etc.) +5. Demais arquivos + +--- + +## Atualizar `mkdocs.yml` + +Adicionar novos arquivos na navegação: + +```yaml +nav: + - Arquitetura: + - PostgreSQL: arquitetura/postgresql.md # NOVO + - Módulos: + - Data Platform: modulos/data-platform.md # NOVO + - Workflows: + - Airflow DAGs: workflows/airflow-dags.md # NOVO +``` + +--- + +## Verificação Final + +1. `mkdocs serve` - verificar renderização local +2. Validar todos os links internos +3. Testar diagramas Mermaid +4. Buscar referências restantes a "scraper" ou "typesense" (repos antigos) +5. Verificar consistência: "PostgreSQL" como "fonte de verdade" +6. Verificar que "HuggingFace" está descrito como "distribuição" + +--- + +## Arquivos Críticos (Leitura Recomendada Durante Implementação) + +### data-platform +- `src/data_platform/managers/postgres_manager.py` - Acesso PostgreSQL +- `src/data_platform/managers/storage_adapter.py` - Dual-write +- `src/data_platform/jobs/typesense/sync_job.py` - Sync Typesense +- `src/data_platform/dags/sync_postgres_to_huggingface.py` - DAG HF + +### infra +- `terraform/cloud_sql.tf` - Config PostgreSQL +- `terraform/composer.tf` - Config Airflow +- `terraform/embeddings-api.tf` - Config Embeddings diff --git a/docs/arquitetura/componentes-estruturantes.md b/docs/arquitetura/componentes-estruturantes.md index 862457d..36163e6 100644 --- a/docs/arquitetura/componentes-estruturantes.md +++ b/docs/arquitetura/componentes-estruturantes.md @@ -1,10 +1,11 @@ # Componentes Estruturantes -O DestaquesGovbr possui três componentes estruturantes que definem a organização dos dados: +O DestaquesGovbr possui quatro componentes estruturantes que definem a organização dos dados: 1. **Árvore Temática** - Classificação hierárquica de notícias -2. **Catálogo de Órgãos** - Lista de 156 agências governamentais -3. **Dataset HuggingFace** - Fonte de verdade dos dados +2. **Catálogo de Órgãos** - Lista de 158 agências governamentais +3. **PostgreSQL** - Fonte de verdade dos dados +4. **HuggingFace** - Distribuição de dados abertos ## 1. Árvore Temática @@ -54,14 +55,15 @@ Nível 1 (Tema) → Nível 2 (Subtema) → Nível 3 (Tópico) ### Arquivos -A árvore temática está duplicada em dois repositórios (sincronização manual por enquanto): +A árvore temática está armazenada no PostgreSQL e duplicada em repositórios (sincronização via pipeline): -| Repositório | Arquivo | Formato | -|-------------|---------|---------| -| scraper | `src/enrichment/themes_tree.yaml` | YAML plano | +| Local | Arquivo/Tabela | Formato | +|-------|----------------|---------| +| PostgreSQL | tabela `themes` | Normalizado (200+ registros) | +| data-platform | `src/data_platform/enrichment/themes_tree.yaml` | YAML plano | | portal | `src/lib/themes.yaml` | YAML estruturado | -#### Formato no Scraper (`themes_tree.yaml`) +#### Formato no Data Platform (`themes_tree.yaml`) ```yaml 01 - Economia e Finanças: 01.01 - Política Econômica: @@ -122,13 +124,14 @@ Cada órgão possui: ### Arquivos -| Repositório | Arquivo | Conteúdo | -|-------------|---------|----------| -| agencies | `agencies.yaml` | Dados dos 156 órgãos | +| Local | Arquivo/Tabela | Conteúdo | +|-------|----------------|----------| +| PostgreSQL | tabela `agencies` | Dados dos 158 órgãos (normalizado) | +| agencies | `agencies.yaml` | Dados fonte dos órgãos | | agencies | `hierarchy.yaml` | Árvore hierárquica | | portal | `src/lib/agencies.yaml` | Cópia (sincronização manual) | -| scraper | `src/scraper/agencies.yaml` | Mapeamento ID → Nome | -| scraper | `src/scraper/site_urls.yaml` | URLs de raspagem | +| data-platform | `src/data_platform/scrapers/agencies.yaml` | Mapeamento ID → Nome | +| data-platform | `src/data_platform/scrapers/site_urls.yaml` | URLs de raspagem | ### Exemplo de Entrada @@ -192,21 +195,42 @@ Automatizar sincronização: --- -## 3. Dataset HuggingFace +## 3. PostgreSQL (Fonte de Verdade) ### Visão Geral -O dataset no HuggingFace é a **fonte de verdade** de todos os dados do sistema. +O PostgreSQL (Cloud SQL) é a **fonte de verdade central** de todos os dados do sistema. -**URL**: [huggingface.co/datasets/nitaibezerra/govbrnews](https://huggingface.co/datasets/nitaibezerra/govbrnews) +**Instância**: `destaquesgovbr-postgres` (southamerica-east1) ### Características | Característica | Valor | |----------------|-------| | Documentos | ~300.000+ | +| Tabelas | 4 (agencies, themes, news, sync_log) | | Atualização | Diária (4AM UTC) | -| Formato | Parquet + CSV | +| Backup | Diário com PITR 7 dias | + +→ Veja detalhes completos em [postgresql.md](postgresql.md) + +--- + +## 4. HuggingFace (Distribuição) + +### Visão Geral + +O dataset no HuggingFace é a **camada de distribuição** de dados abertos, sincronizada diariamente a partir do PostgreSQL. + +**URL**: [huggingface.co/datasets/nitaibezerra/govbrnews](https://huggingface.co/datasets/nitaibezerra/govbrnews) + +### Características + +| Característica | Valor | +|----------------|-------| +| Documentos | ~300.000+ | +| Sincronização | Diária (6AM UTC via Airflow) | +| Formato | Parquet (shards incrementais) | | Versionamento | Automático pelo HF | ### Schema do Dataset @@ -285,11 +309,10 @@ govbrnews/ ### Uso no Sistema -1. **Scraper**: Insere e atualiza registros -2. **Typesense**: Indexa para busca full-text -3. **Portal**: Consome via Typesense -4. **Streamlit**: Análises e visualizações -5. **Pesquisadores**: Download para análises externas +1. **Comunidade**: Download para análises externas +2. **Pesquisadores**: Estudos acadêmicos +3. **Streamlit**: Visualizações públicas +4. **Dados abertos**: Acesso transparente aos dados governamentais ### Dataset Reduzido @@ -305,40 +328,50 @@ Existe também uma versão reduzida com apenas 4 colunas para análises rápidas graph TB subgraph "Componentes Estruturantes" AT[Árvore Temática
25 temas × 3 níveis] - CO[Catálogo de Órgãos
156 agências] - DS[Dataset HuggingFace
~300k docs] + CO[Catálogo de Órgãos
158 agências] + PG[(PostgreSQL
Fonte de Verdade)] + HF[(HuggingFace
Distribuição)] end subgraph "Uso no Sistema" SC[Scraper] CF[Cogfy] - PO[Portal] + EMB[Embeddings] TS[Typesense] + PO[Portal] end CO -->|URLs de raspagem| SC AT -->|Classificação| CF - SC -->|Insere dados| DS - CF -->|Enriquece| DS - DS -->|Indexa| TS + SC -->|Insere dados| PG + CF -->|Enriquece| PG + EMB -->|Vetores| PG + PG -->|Indexa| TS + PG -->|Sync diário| HF TS -->|Busca| PO AT -->|Filtros| PO CO -->|Filtros| PO ``` -## Sincronização Atual vs Futura +## Sincronização -### Atual (Manual) +### Dados (Automatizado) +``` +Scraper → PostgreSQL → Typesense (sync diário) + → HuggingFace (DAG Airflow 6AM UTC) +``` + +### Componentes Estruturantes (Manual) ``` agencies.yaml → Cópia manual → portal/agencies.yaml - → scraper/agencies.yaml - → scraper/site_urls.yaml + → data-platform/agencies.yaml themes_tree.yaml → Cópia manual → portal/themes.yaml + → PostgreSQL (via script) ``` -### Futuro (Automatizado) +### Meta Futura ``` -agencies → GitHub Action → portal + scraper -destaquesgovbr-themes → GitHub Action → portal + scraper +agencies repo → GitHub Action → portal + data-platform + PostgreSQL +themes repo → GitHub Action → portal + data-platform + PostgreSQL ``` diff --git a/docs/arquitetura/fluxo-de-dados.md b/docs/arquitetura/fluxo-de-dados.md index d7df0ab..9dbe9a5 100644 --- a/docs/arquitetura/fluxo-de-dados.md +++ b/docs/arquitetura/fluxo-de-dados.md @@ -12,9 +12,12 @@ sequenceDiagram participant SC as Scraper Container participant GOV as Sites gov.br participant EBC as Sites EBC - participant HF as HuggingFace + participant PG as PostgreSQL participant CF as Cogfy API + participant EMB as Embeddings API participant TS as Typesense + participant AF as Airflow (6AM UTC) + participant HF as HuggingFace rect rgb(227, 242, 253) Note over GH,SC: ETAPA 1: Scraping gov.br @@ -23,7 +26,7 @@ sequenceDiagram GOV-->>SC: HTML das páginas SC->>SC: Parse HTML → Markdown SC->>SC: Gera unique_id (MD5) - SC->>HF: dataset.insert(new_articles) + SC->>PG: postgres.insert(new_articles) end rect rgb(255, 243, 224) @@ -31,13 +34,13 @@ sequenceDiagram SC->>EBC: Requisições HTTP (sites EBC) EBC-->>SC: HTML das páginas SC->>SC: Parse especializado EBC - SC->>HF: dataset.insert(ebc_articles, allow_update=True) + SC->>PG: postgres.insert(ebc_articles, allow_update=True) end rect rgb(255, 253, 231) Note over SC,CF: ETAPA 3: Upload para Cogfy - SC->>HF: dataset.get(date_range) - HF-->>SC: DataFrame com artigos + SC->>PG: get_news(date_range) + PG-->>SC: Registros SC->>CF: POST /records (batch 1000) CF-->>SC: IDs dos registros end @@ -50,21 +53,38 @@ sequenceDiagram end rect rgb(252, 228, 236) - Note over SC,HF: ETAPA 5: Enriquecimento + Note over SC,PG: ETAPA 5: Enriquecimento SC->>CF: GET /records (busca por unique_id) CF-->>SC: themes + summary SC->>SC: Mapeia códigos → labels SC->>SC: Calcula most_specific_theme - SC->>HF: dataset.update(enriched_df) + SC->>PG: postgres.update(enriched_data) + end + + rect rgb(255, 248, 225) + Note over SC,EMB: ETAPA 6: Embeddings + SC->>PG: get_news_without_embeddings() + PG-->>SC: Notícias sem vetores + SC->>EMB: POST /embed (batch 100) + EMB-->>SC: Vetores 768-dim + SC->>PG: postgres.update(embeddings) end rect rgb(243, 229, 245) - Note over TS,HF: ETAPA 6: Indexação Typesense - GH->>TS: Trigger typesense-daily-load - TS->>HF: Download dataset - HF-->>TS: Parquet files + Note over TS,PG: ETAPA 7: Indexação Typesense + GH->>TS: Trigger typesense-sync + TS->>PG: iter_news_for_typesense() + PG-->>TS: Batches de 5000 TS->>TS: Upsert documentos end + + rect rgb(225, 245, 254) + Note over AF,HF: ETAPA 8: Sync HuggingFace + AF->>PG: Query novos registros + PG-->>AF: Registros do dia anterior + AF->>AF: Cria parquet shard + AF->>HF: Upload shard + end ``` ## Etapas Detalhadas @@ -74,19 +94,19 @@ sequenceDiagram **Workflow**: `main-workflow.yaml` → job `scraper` ```bash -python src/main.py scrape --start-date YYYY-MM-DD --end-date YYYY-MM-DD +data-platform scrape --start-date YYYY-MM-DD --end-date YYYY-MM-DD ``` **Processo**: -1. Carrega URLs de `src/scraper/site_urls.yaml` (~160+ URLs) +1. Carrega URLs de `src/data_platform/scrapers/site_urls.yaml` (~160+ URLs) 2. Para cada URL, instancia `WebScraper` 3. Navega por páginas com paginação (`?b_start:int=N`) 4. Extrai campos: title, date, url, image, category, tags 5. Faz fetch do conteúdo completo de cada notícia 6. Converte HTML → Markdown com `markdownify` 7. Gera `unique_id = MD5(agency + published_at + title)` -8. Insere no HuggingFace via `DatasetManager.insert()` +8. Insere no PostgreSQL via `PostgresManager.insert()` **Retry Logic**: ```python @@ -99,7 +119,7 @@ def fetch_page(url): ... **Workflow**: `main-workflow.yaml` → job `ebc-scraper` ```bash -python src/main.py scrape-ebc --start-date YYYY-MM-DD --end-date YYYY-MM-DD --allow-update +data-platform scrape-ebc --start-date YYYY-MM-DD --end-date YYYY-MM-DD --allow-update ``` **Diferenças**: @@ -113,12 +133,12 @@ python src/main.py scrape-ebc --start-date YYYY-MM-DD --end-date YYYY-MM-DD --al **Workflow**: `main-workflow.yaml` → job `upload-to-cogfy` ```bash -python src/upload_to_cogfy_manager.py --start-date YYYY-MM-DD --end-date YYYY-MM-DD +data-platform upload-cogfy --start-date YYYY-MM-DD --end-date YYYY-MM-DD ``` **Processo**: -1. Carrega artigos do HuggingFace por intervalo de datas +1. Carrega artigos do PostgreSQL por intervalo de datas 2. Converte campos para formato Cogfy: - `published_at` → datetime UTC @@ -142,7 +162,7 @@ O Cogfy executa: **Workflow**: `main-workflow.yaml` → job `enrich-themes` ```bash -python src/enrichment_manager.py --start-date YYYY-MM-DD --end-date YYYY-MM-DD +data-platform enrich --start-date YYYY-MM-DD --end-date YYYY-MM-DD ``` **Processo**: @@ -156,21 +176,51 @@ python src/enrichment_manager.py --start-date YYYY-MM-DD --end-date YYYY-MM-DD - `theme_1_level_3` (text) → código e label - `summary` (text) 4. Calcula `most_specific_theme` (prioridade: L3 > L2 > L1) -5. Atualiza dataset no HuggingFace +5. Atualiza PostgreSQL -### Etapa 6: Indexação Typesense +### Etapa 6: Embeddings -**Workflow**: `typesense-daily-load.yml` (10AM UTC) +**Workflow**: `main-workflow.yaml` → job `generate-embeddings` ```bash -python python/scripts/load_data.py --mode incremental --days 7 +data-platform generate-embeddings --start-date YYYY-MM-DD +``` + +**Processo**: + +1. Busca notícias sem embeddings no PostgreSQL +2. Prepara texto: `title + summary` (fallback para `content`) +3. Envia para Embeddings API em batches de 100 +4. Recebe vetores 768-dim do modelo `paraphrase-multilingual-mpnet-base-v2` +5. Atualiza `content_embedding` no PostgreSQL + +### Etapa 7: Indexação Typesense + +**Workflow**: `typesense-maintenance-sync.yaml` (10AM UTC) + +```bash +data-platform sync-typesense --start-date YYYY-MM-DD ``` **Processo**: 1. Conecta ao Typesense em produção -2. Baixa dataset do HuggingFace (últimos 7 dias) -3. Faz upsert dos documentos na collection `news` +2. Lê dados do PostgreSQL em batches de 5000 +3. Faz upsert dos documentos na collection `news` (incluindo embeddings) + +### Etapa 8: Sync HuggingFace + +**DAG Airflow**: `sync_postgres_to_huggingface` (6AM UTC) + +**Processo**: + +1. Query notícias do dia anterior no PostgreSQL +2. Consulta IDs existentes no HuggingFace via Dataset Viewer API +3. Filtra apenas novos registros +4. Cria parquet shard com novos dados +5. Upload do shard para HuggingFace + +→ Veja detalhes em [workflows/airflow-dags.md](../workflows/airflow-dags.md) ## Dados de Entrada e Saída @@ -188,12 +238,15 @@ python python/scripts/load_data.py --mode incremental --days 7 ``` -### Saída (HuggingFace Dataset) +### Saída (PostgreSQL / News) ```json { + "id": 123456, "unique_id": "abc123def456", - "agency": "gestao", + "agency_id": 45, + "agency_key": "gestao", + "agency_name": "Ministério da Gestão", "published_at": "2024-12-02T10:00:00Z", "updated_datetime": "2024-12-02T14:30:00Z", "extracted_at": "2024-12-02T07:00:00Z", @@ -202,19 +255,17 @@ python python/scripts/load_data.py --mode incremental --days 7 "editorial_lead": "Linha fina com contexto", "url": "https://www.gov.br/gestao/...", "content": "# Título\n\nConteúdo em Markdown...", - "image": "https://www.gov.br/.../imagem.jpg", + "image_url": "https://www.gov.br/.../imagem.jpg", "video_url": null, "category": "Notícias", "tags": ["tag1", "tag2"], - "theme_1_level_1_code": "01", - "theme_1_level_1_label": "Economia e Finanças", - "theme_1_level_2_code": "01.01", - "theme_1_level_2_label": "Política Econômica", - "theme_1_level_3_code": "01.01.01", - "theme_1_level_3_label": "Política Fiscal", - "most_specific_theme_code": "01.01.01", - "most_specific_theme_label": "Política Fiscal", - "summary": "Resumo gerado por AI..." + "theme_l1_id": 1, + "theme_l2_id": 5, + "theme_l3_id": 15, + "most_specific_theme_id": 15, + "summary": "Resumo gerado por AI...", + "content_embedding": [0.123, -0.456, ...], // 768 dimensões + "embedding_generated_at": "2024-12-02T08:00:00Z" } ``` @@ -229,9 +280,14 @@ python python/scripts/load_data.py --mode incremental --days 7 - Verificação de status antes de buscar resultados - Fallback para valores vazios se inferência falhar -### HuggingFace -- Retry em push (5 tentativas) -- Deduplicação por `unique_id` +### PostgreSQL +- Connection pooling com retry +- Deduplicação por `unique_id` (ON CONFLICT) +- Transações para operações batch + +### HuggingFace (Sync) +- Incremental via parquet shards +- Deduplicação via Dataset Viewer API ## Monitoramento @@ -249,18 +305,35 @@ python python/scripts/load_data.py --mode incremental --days 7 ### Scraping de período específico ```bash +# Via CLI +data-platform scrape --start-date 2024-01-01 --end-date 2024-01-31 + # Via GitHub Actions -gh workflow run scraper-dispatch.yaml \ - -f min-date=2024-01-01 \ - -f max-date=2024-01-31 +gh workflow run main-workflow.yaml \ + -f start-date=2024-01-01 \ + -f end-date=2024-01-31 ``` -### Resync com Cogfy +### Enriquecimento manual ```bash -gh workflow run cogfy-sync-dispatch.yaml +data-platform enrich --start-date 2024-01-01 --force +``` + +### Geração de embeddings +```bash +data-platform generate-embeddings --start-date 2024-01-01 +``` + +### Sync Typesense +```bash +# Incremental +data-platform sync-typesense --start-date 2024-01-01 + +# Full reload +data-platform sync-typesense --full-sync ``` -### Reload completo do Typesense +### Reload completo do Typesense (via GitHub Actions) ```bash gh workflow run typesense-full-reload.yaml \ -f confirm=DELETE diff --git a/docs/arquitetura/postgresql.md b/docs/arquitetura/postgresql.md new file mode 100644 index 0000000..e00a749 --- /dev/null +++ b/docs/arquitetura/postgresql.md @@ -0,0 +1,308 @@ +# PostgreSQL - Fonte de Verdade + +O PostgreSQL (Cloud SQL) é a **fonte de verdade central** do sistema DestaquesGovBr, armazenando todas as notícias coletadas, enriquecidas e processadas. + +!!! info "Cloud SQL" + **Instância**: `destaquesgovbr-postgres` + **Região**: `southamerica-east1` (São Paulo) + **Versão**: PostgreSQL 15 + +## Schema + +O banco de dados possui **4 tabelas principais** com normalização parcial. + +```mermaid +erDiagram + agencies ||--o{ news : "has" + themes ||--o{ news : "theme_l1" + themes ||--o{ news : "theme_l2" + themes ||--o{ news : "theme_l3" + themes ||--o{ news : "most_specific" + + agencies { + int id PK + string key UK + string name + string type + string parent_key + string url + timestamp created_at + } + + themes { + int id PK + string code UK + string label + string full_name + int level + string parent_code + timestamp created_at + } + + news { + int id PK + string unique_id UK + int agency_id FK + int theme_l1_id FK + int theme_l2_id FK + int theme_l3_id FK + int most_specific_theme_id FK + string title + string url + text content + string summary + vector content_embedding + timestamp published_at + timestamp created_at + } + + sync_log { + int id PK + string operation + string status + json details + timestamp created_at + } +``` + +### Tabela `agencies` + +Dados mestres de agências governamentais (158 registros). + +| Coluna | Tipo | Descrição | +|--------|------|-----------| +| `id` | `SERIAL` | Chave primária | +| `key` | `VARCHAR(100)` | Identificador único (ex: `mec`, `saude`) | +| `name` | `VARCHAR(255)` | Nome completo da agência | +| `type` | `VARCHAR(50)` | Tipo (ministério, autarquia, etc) | +| `parent_key` | `VARCHAR(100)` | Chave da agência pai (hierarquia) | +| `url` | `VARCHAR(500)` | URL do portal da agência | +| `created_at` | `TIMESTAMP` | Data de criação | + +**Índices**: `key` (unique), `parent_key` + +### Tabela `themes` + +Taxonomia hierárquica de temas em 3 níveis (200+ registros). + +| Coluna | Tipo | Descrição | +|--------|------|-----------| +| `id` | `SERIAL` | Chave primária | +| `code` | `VARCHAR(20)` | Código hierárquico (ex: `01`, `01.01`, `01.01.01`) | +| `label` | `VARCHAR(255)` | Nome curto do tema | +| `full_name` | `VARCHAR(500)` | Nome completo incluindo hierarquia | +| `level` | `INTEGER` | Nível hierárquico (1, 2 ou 3) | +| `parent_code` | `VARCHAR(20)` | Código do tema pai | +| `created_at` | `TIMESTAMP` | Data de criação | + +**Índices**: `code` (unique), `level`, `parent_code` + +**Exemplo de hierarquia**: +``` +01 - Economia e Finanças (level 1) + 01.01 - Política Econômica (level 2) + 01.01.01 - Política Fiscal (level 3) +``` + +### Tabela `news` + +Notícias coletadas e enriquecidas (300k+ registros). + +| Coluna | Tipo | Descrição | +|--------|------|-----------| +| **Chaves** | | | +| `id` | `SERIAL` | Chave primária | +| `unique_id` | `VARCHAR(32)` | MD5(agency + published_at + title) | +| **Foreign Keys** | | | +| `agency_id` | `INTEGER` | FK para `agencies.id` | +| `theme_l1_id` | `INTEGER` | Tema nível 1 | +| `theme_l2_id` | `INTEGER` | Tema nível 2 | +| `theme_l3_id` | `INTEGER` | Tema nível 3 | +| `most_specific_theme_id` | `INTEGER` | Tema mais específico (L3 > L2 > L1) | +| **Conteúdo** | | | +| `title` | `VARCHAR(500)` | Título da notícia | +| `url` | `VARCHAR(1000)` | URL original | +| `image_url` | `VARCHAR(1000)` | URL da imagem | +| `video_url` | `VARCHAR(1000)` | URL do vídeo | +| `category` | `VARCHAR(100)` | Categoria original do site | +| `tags` | `TEXT[]` | Tags originais do site | +| `content` | `TEXT` | Conteúdo completo em Markdown | +| `editorial_lead` | `TEXT` | Lead editorial | +| `subtitle` | `VARCHAR(500)` | Subtítulo | +| `summary` | `TEXT` | Resumo gerado pelo Cogfy | +| **Timestamps** | | | +| `published_at` | `TIMESTAMP` | Data de publicação | +| `updated_datetime` | `TIMESTAMP` | Última atualização no site | +| `extracted_at` | `TIMESTAMP` | Data de extração | +| `created_at` | `TIMESTAMP` | Data de inserção no BD | +| `updated_at` | `TIMESTAMP` | Última atualização no BD | +| **Denormalizados** | | | +| `agency_key` | `VARCHAR(100)` | Key da agência (performance) | +| `agency_name` | `VARCHAR(255)` | Nome da agência (performance) | +| **Embeddings** | | | +| `content_embedding` | `VECTOR(768)` | Embedding do conteúdo | +| `embedding_generated_at` | `TIMESTAMP` | Data de geração do embedding | + +**Índices**: + +- `unique_id` (unique) +- `published_at DESC` (ordenação) +- `agency_id` (filtro por agência) +- `most_specific_theme_id` (filtro por tema) +- `(agency_id, published_at DESC)` (composto) +- `agency_key` (filtro rápido) +- Full-text search: `tsvector('portuguese', title || ' ' || content)` + +### Tabela `sync_log` + +Log de operações de sincronização (auditoria). + +| Coluna | Tipo | Descrição | +|--------|------|-----------| +| `id` | `SERIAL` | Chave primária | +| `operation` | `VARCHAR(50)` | Tipo (scrape, enrich, typesense_sync, hf_sync) | +| `status` | `VARCHAR(20)` | Status (started, completed, failed) | +| `details` | `JSONB` | Metadados (count, errors, duration) | +| `created_at` | `TIMESTAMP` | Timestamp da operação | + +## Configuração Cloud SQL + +### Instância + +| Configuração | Valor | +|--------------|-------| +| **Nome** | `destaquesgovbr-postgres` | +| **Versão** | PostgreSQL 15 | +| **Tier** | `db-custom-1-3840` (1 vCPU, 3.75GB RAM) | +| **Storage** | 50GB SSD (auto-resize até 500GB) | +| **Região** | `southamerica-east1` | +| **Availability** | ZONAL | +| **Deletion Protection** | Habilitado | + +### Rede + +```mermaid +graph LR + subgraph "VPC Network" + PG[(Cloud SQL
Private IP)] + CR[Cloud Run
Portal] + CE[Compute Engine
Typesense] + GHA[GitHub Actions
Cloud SQL Proxy] + end + + CR -.->|VPC Connector| PG + CE -->|Internal IP| PG + GHA -->|SQL Proxy| PG +``` + +- **IP Privado**: Via VPC peering +- **Acesso externo**: Apenas via Cloud SQL Proxy + +### Backup + +| Configuração | Valor | +|--------------|-------| +| **Backups automáticos** | Diários às 3AM UTC | +| **Point-in-time recovery** | Habilitado (7 dias) | +| **Retenção** | 30 backups | +| **Auto-resize** | Até 500GB | + +### Performance Flags + +```sql +-- Conexões +max_connections = 100 + +-- Memória +shared_buffers = 256MB +effective_cache_size = 336MB +work_mem = 2.5MB +maintenance_work_mem = 64MB + +-- Logging +log_min_duration_statement = 1000 -- Log queries > 1s +``` + +### Query Insights + +- **Habilitado**: Sim +- **Query string length**: 1024 caracteres +- **Application tags**: Habilitadas + +## Secrets + +As credenciais são armazenadas no Secret Manager: + +| Secret ID | Conteúdo | +|-----------|----------| +| `govbrnews-postgres-connection-string` | `postgresql://user:pass@host:5432/govbrnews` | +| `govbrnews-postgres-host` | IP privado da instância | +| `govbrnews-postgres-password` | Senha do usuário `govbrnews_app` | + +## Acesso + +### Pelo Código (data-platform) + +```python +from data_platform.managers.postgres_manager import PostgresManager + +pm = PostgresManager() # Lê DATABASE_URL ou Secret Manager +pm.load_cache() + +# Operações +news = pm.get(filters={"agency_key": "mec"}, limit=100) +pm.insert(news_list, allow_update=True) +``` + +### Variáveis de Ambiente + +```bash +# Conexão direta (desenvolvimento) +DATABASE_URL=postgresql://govbrnews_app:xxx@10.x.x.x:5432/govbrnews + +# Ou via Secret Manager (produção) +# A aplicação busca automaticamente se DATABASE_URL não estiver definida +``` + +### Via Cloud SQL Proxy (local) + +```bash +# Instalar proxy +curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 +chmod +x cloud_sql_proxy + +# Conectar +./cloud_sql_proxy -instances=inspire-7-finep:southamerica-east1:destaquesgovbr-postgres=tcp:5432 + +# Em outro terminal +psql -h localhost -U govbrnews_app -d govbrnews +``` + +## Custos Estimados + +| Componente | Custo/mês | +|------------|-----------| +| Instância (db-custom-1-3840) | ~$35 | +| Storage (50GB SSD) | ~$8.50 | +| Backups (30 dias) | ~$5 | +| **Total** | **~$48.50** | + +## Normalização Parcial + +O schema adota normalização **parcial** para balancear integridade e performance: + +### Normalizado + +- **agencies**: Tabela de lookup separada +- **themes**: Taxonomia hierárquica com self-reference + +### Denormalizado (performance) + +- `agency_key` e `agency_name` em `news`: Evita JOINs em queries frequentes +- Índice composto `(agency_id, published_at)`: Otimiza filtros combinados + +### Vantagens + +1. **Integridade**: FKs garantem consistência +2. **Performance**: Campos denormalizados evitam JOINs +3. **Flexibilidade**: Normalização permite queries complexas quando necessário diff --git a/docs/arquitetura/visao-geral.md b/docs/arquitetura/visao-geral.md index 0ff26bc..422694d 100644 --- a/docs/arquitetura/visao-geral.md +++ b/docs/arquitetura/visao-geral.md @@ -2,70 +2,86 @@ ## Resumo -O DestaquesGovbr é uma plataforma de agregação e enriquecimento de notícias governamentais composta por 5 camadas principais: +O DestaquesGovbr é uma plataforma de agregação e enriquecimento de notícias governamentais composta por 7 camadas principais: 1. **Coleta** - Raspagem automatizada de ~160+ sites gov.br -2. **Enriquecimento** - Classificação temática e sumarização via AI/LLM -3. **Armazenamento** - Dataset no HuggingFace como fonte de verdade -4. **Indexação** - Typesense para busca full-text -5. **Apresentação** - Portal Next.js e apps de análise +2. **Armazenamento** - PostgreSQL (Cloud SQL) como fonte de verdade +3. **Enriquecimento** - Classificação temática e sumarização via Cogfy/LLM +4. **Embeddings** - Geração de vetores 768-dim para busca semântica +5. **Indexação** - Typesense para busca full-text e vetorial +6. **Distribuição** - HuggingFace para dados abertos +7. **Apresentação** - Portal Next.js e apps de análise ## Diagrama de Arquitetura ```mermaid flowchart TB subgraph COLETA["1. Coleta"] - A[160+ Sites gov.br] -->|Raspagem diária 4AM UTC| B[WebScraper Python] + A[160+ Sites gov.br] -->|Raspagem diária 4AM UTC| B[Scraper Python] A2[Sites EBC] -->|Raspagem| B - B --> C[DatasetManager] end - subgraph ENRIQUECIMENTO["2. Enriquecimento AI"] + subgraph ARMAZENAMENTO["2. Armazenamento - Fonte de Verdade"] + B -->|Insert| C[(PostgreSQL
Cloud SQL)] + end + + subgraph ENRIQUECIMENTO["3. Enriquecimento AI"] C -->|Upload batch| D[Cogfy API] D -->|LLM Inference| E[Classificação Temática] D -->|LLM Inference| F[Resumo Automático] E --> G[EnrichmentManager] F --> G + G -->|Update| C + end + + subgraph EMBEDDINGS["4. Embeddings"] + C -->|Textos| H[Embeddings API] + H -->|Vetores 768-dim| C end - subgraph ARMAZENAMENTO["3. Armazenamento ~300k docs"] - G -->|Push| H[(HuggingFace Dataset)] + subgraph INDEXACAO["5. Indexação - Busca"] + C -->|Sync| I[(Typesense)] end - subgraph INDEXACAO["4. Indexação - Busca full-text"] - H -->|Sync diário| I[(Typesense)] + subgraph DISTRIBUICAO["6. Distribuição"] + C -->|DAG Airflow| J[(HuggingFace
Dados Abertos)] end - subgraph APRESENTACAO["5. Apresentação"] - I -->|API Search| J[Portal Next.js] - H -->|Análise dados| K[Streamlit App] + subgraph APRESENTACAO["7. Apresentação"] + I -->|API Search| K[Portal Next.js] + J -->|Análise dados| L[Streamlit App] end subgraph INFRA["Infraestrutura GCP"] - L[Cloud Run] -.-> J - M[Compute Engine] -.-> I - N[Artifact Registry] - O[Secret Manager] + M[Cloud Run] -.-> K + M2[Cloud Run] -.-> H + N[Compute Engine] -.-> I + O[Cloud SQL] -.-> C + P[Cloud Composer] -.-> J + Q[Secret Manager] end style COLETA fill:#e3f2fd - style ENRIQUECIMENTO fill:#fff3e0 style ARMAZENAMENTO fill:#e8f5e9 + style ENRIQUECIMENTO fill:#fff3e0 + style EMBEDDINGS fill:#fff8e1 style INDEXACAO fill:#fce4ec + style DISTRIBUICAO fill:#e1f5fe style APRESENTACAO fill:#f3e5f5 style INFRA fill:#eceff1 ``` ## Componentes por Camada -### 1. Coleta (`scraper`) +### 1. Coleta (`data-platform`) | Componente | Arquivo | Responsabilidade | |------------|---------|------------------| -| WebScraper | `src/scraper/webscraper.py` | Raspagem genérica de sites gov.br | -| EBC Scraper | `src/scraper/ebc_webscraper.py` | Raspagem especializada da EBC | -| ScrapeManager | `src/scraper/scrape_manager.py` | Orquestração paralela/sequencial | -| DatasetManager | `src/dataset_manager.py` | Insert/Update no HuggingFace | +| WebScraper | `src/data_platform/scrapers/webscraper.py` | Raspagem genérica de sites gov.br | +| EBCScraper | `src/data_platform/scrapers/ebc_webscraper.py` | Raspagem especializada da EBC | +| ScrapeManager | `src/data_platform/scrapers/scrape_manager.py` | Orquestração paralela/sequencial | +| PostgresManager | `src/data_platform/managers/postgres_manager.py` | Insert/Update no PostgreSQL | +| StorageAdapter | `src/data_platform/managers/storage_adapter.py` | Abstração de storage (PG/HF/dual) | **Dados extraídos por notícia:** @@ -86,13 +102,26 @@ flowchart TB | `category` | Categoria original do site | | `tags` | Tags/keywords do site | -### 2. Enriquecimento (`scraper` + Cogfy) +### 2. Armazenamento (PostgreSQL) + +**Instância Cloud SQL**: `destaquesgovbr-postgres` (PostgreSQL 15) + +| Tabela | Registros | Descrição | +|--------|-----------|-----------| +| `agencies` | 158 | Órgãos governamentais | +| `themes` | 200+ | Taxonomia temática (3 níveis) | +| `news` | 300k+ | Notícias coletadas e enriquecidas | +| `sync_log` | - | Log de operações | + +→ Veja detalhes em [postgresql.md](postgresql.md) + +### 3. Enriquecimento (`data-platform` + Cogfy) | Componente | Arquivo | Responsabilidade | |------------|---------|------------------| -| CogfyManager | `src/cogfy_manager.py` | Cliente da API Cogfy | -| UploadToCogfy | `src/upload_to_cogfy_manager.py` | Envio de notícias para inferência | -| EnrichmentManager | `src/enrichment_manager.py` | Busca resultados e atualiza dataset | +| CogfyManager | `src/data_platform/cogfy/cogfy_manager.py` | Cliente da API Cogfy | +| UploadManager | `src/data_platform/cogfy/upload_manager.py` | Envio de notícias para inferência | +| EnrichmentManager | `src/data_platform/cogfy/enrichment_manager.py` | Busca resultados e atualiza PostgreSQL | **Campos enriquecidos:** @@ -102,26 +131,49 @@ flowchart TB - `most_specific_theme_code/label` - Tema mais específico disponível - `summary` - Resumo gerado por LLM -### 3. Armazenamento (HuggingFace) +### 4. Embeddings (`data-platform`) -**Dataset principal**: [nitaibezerra/govbrnews](https://huggingface.co/datasets/nitaibezerra/govbrnews) +| Componente | Arquivo | Responsabilidade | +|------------|---------|------------------| +| EmbeddingGenerator | `src/data_platform/jobs/embeddings/embedding_generator.py` | Geração de vetores | +| Embeddings API | Cloud Run | Modelo `paraphrase-multilingual-mpnet-base-v2` | -- ~300.000+ documentos -- Atualização diária automatizada -- Exportação em CSV por agência e ano -- Versionamento automático pelo HuggingFace +**Características:** +- Vetores de 768 dimensões +- Input: `title + summary` (fallback para `content`) +- Armazenados em `news.content_embedding` (tipo `VECTOR`) -### 4. Indexação (Typesense) +### 5. Indexação (Typesense) + +| Componente | Arquivo | Responsabilidade | +|------------|---------|------------------| +| TypesenseClient | `src/data_platform/typesense/client.py` | Conexão com Typesense | +| TypesenseIndexer | `src/data_platform/typesense/indexer.py` | Indexação de documentos | +| SyncJob | `src/data_platform/jobs/typesense/sync_job.py` | Sincronização PostgreSQL → Typesense | **Collection**: `news` Configurado para: - Busca full-text em `title` e `content` +- Busca vetorial via `content_embedding` (768-dim) - Filtros facetados por `agency`, `theme_*`, `published_at` - Ordenação por relevância e data -### 5. Apresentação +### 6. Distribuição (HuggingFace) + +**Dataset principal**: [nitaibezerra/govbrnews](https://huggingface.co/datasets/nitaibezerra/govbrnews) + +- ~300.000+ documentos +- Sincronização diária via DAG Airflow +- Abordagem incremental (parquet shards) +- Versionamento automático pelo HuggingFace + +**DAG**: `sync_postgres_to_huggingface` (6 AM UTC) + +→ Veja detalhes em [workflows/airflow-dags.md](../workflows/airflow-dags.md) + +### 7. Apresentação | App | Tecnologia | URL | |-----|------------|-----| @@ -134,37 +186,49 @@ Configurado para: sequenceDiagram participant Cron as GitHub Actions (4AM UTC) participant Scraper as Scraper Python + participant PG as PostgreSQL participant Cogfy as Cogfy API - participant HF as HuggingFace + participant EMB as Embeddings API participant TS as Typesense + participant Airflow as Cloud Composer (6AM UTC) + participant HF as HuggingFace Note over Cron: Trigger diário Cron->>Scraper: main-workflow.yaml Scraper->>Scraper: scrape gov.br (160+ sites) Scraper->>Scraper: scrape EBC - Scraper->>HF: Insert novos artigos + Scraper->>PG: Insert novos artigos Scraper->>Cogfy: Upload para inferência Note over Cogfy: Aguarda 20 min para processamento Cogfy-->>Scraper: Themes + Summary - Scraper->>HF: Update com enrichment + Scraper->>PG: Update com enrichment + + Scraper->>EMB: Gerar embeddings + EMB-->>Scraper: Vetores 768-dim + Scraper->>PG: Update com embeddings - Cron->>TS: typesense-daily-load.yml - TS->>HF: Fetch dataset + Cron->>TS: typesense-sync + TS->>PG: Fetch dados TS->>TS: Index documentos - Note over TS: Portal consome dados atualizados + Airflow->>PG: Query novos registros + Airflow->>HF: Upload parquet shard + + Note over TS,HF: Portal e comunidade consomem dados atualizados ``` ## Tecnologias Principais -### Backend (Scraper) +### Backend (Data Platform) -- **Python 3.12+** com Poetry +- **Python 3.11+** com Poetry +- **PostgreSQL 15** (Cloud SQL) com psycopg2 - **BeautifulSoup4** para parsing HTML -- **datasets** (HuggingFace) para gerenciamento de dados +- **datasets** + **huggingface_hub** para sync HF - **requests** com retry logic +- **Apache Airflow 3** (Cloud Composer) ### Frontend (Portal) @@ -176,18 +240,21 @@ sequenceDiagram ### Infraestrutura -- **GCP** - Cloud Run, Compute Engine, VPC +- **GCP** - Cloud Run, Compute Engine, Cloud SQL, Cloud Composer, VPC - **Terraform** - IaC - **Docker** - Containerização - **GitHub Actions** - CI/CD +- **Apache Airflow** - Orquestração de pipelines ## Custos Estimados | Componente | Custo/mês | |------------|-----------| -| Compute Engine (Typesense) | ~$55 | -| Cloud Run (Portal) | ~$12-17 | -| **Total** | **~$70** | +| Cloud SQL (PostgreSQL) | ~$48 | +| Compute Engine (Typesense) | ~$64 | +| Cloud Run (Portal + Embeddings) | ~$15 | +| Cloud Composer (Airflow) | ~$100-150 | +| **Total** | **~$230-280** | ## Próximos Passos diff --git a/docs/index.md b/docs/index.md index 3171c0d..6c230e6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,8 +8,8 @@ O **DestaquesGovbr** é uma plataforma integrada de notícias e informações do - **Centraliza** ~160+ portais governamentais em uma plataforma única - **Classifica** automaticamente notícias usando AI/LLM em 25 temas e 3 níveis hierárquicos -- **Disponibiliza** dados abertos no HuggingFace (~300k+ notícias) -- **Oferece** portal web moderno com busca semântica +- **Armazena** dados em PostgreSQL (fonte de verdade) e distribui via HuggingFace (~300k+ notícias) +- **Oferece** portal web moderno com busca semântica e vetorial ## Quick Start @@ -58,10 +58,14 @@ flowchart LR ```mermaid flowchart LR A[160+ Sites gov.br] -->|Raspagem| B[Scraper] - B -->|Enriquecimento| C[Cogfy/LLM] - C -->|Armazenamento| D[(HuggingFace)] - D -->|Indexação| E[(Typesense)] - E -->|Busca| F[Portal Next.js] + B -->|Armazenamento| C[(PostgreSQL)] + C -->|Enriquecimento| D[Cogfy/LLM] + D --> C + C -->|Embeddings| E[Embeddings API] + E --> C + C -->|Indexação| F[(Typesense)] + F -->|Busca| G[Portal Next.js] + C -->|Sync diário| H[(HuggingFace)] ``` → Veja detalhes em [arquitetura/visao-geral.md](arquitetura/visao-geral.md) @@ -70,11 +74,11 @@ flowchart LR | Repositório | Descrição | Tecnologia | |-------------|-----------|------------| -| [scraper](https://github.com/destaquesgovbr/scraper) | Scraper + Pipeline de dados | Python/Poetry | +| [data-platform](https://github.com/destaquesgovbr/data-platform) | Pipeline de dados (scraper, sync, enrichment) | Python/Poetry | | [portal](https://github.com/destaquesgovbr/portal) | Portal web principal | Next.js 15 | | [infra](https://github.com/destaquesgovbr/infra) | Infraestrutura como código | Terraform/GCP | -| [typesense](https://github.com/destaquesgovbr/typesense) | Typesense para dev local | Docker | | [agencies](https://github.com/destaquesgovbr/agencies) | Dados dos órgãos | YAML | +| [themes](https://github.com/destaquesgovbr/themes) | Taxonomia temática | YAML | ## Recursos Externos diff --git a/docs/modulos/data-platform.md b/docs/modulos/data-platform.md new file mode 100644 index 0000000..7d9d277 --- /dev/null +++ b/docs/modulos/data-platform.md @@ -0,0 +1,274 @@ +# Data Platform + +Repositório centralizado para toda a infraestrutura de dados do DestaquesGovBr. + +!!! info "Repositório" + **GitHub**: [destaquesgovbr/data-platform](https://github.com/destaquesgovbr/data-platform) + +## Visão Geral + +O **data-platform** unifica toda a lógica de dados que anteriormente estava distribuída em múltiplos repositórios (`scraper`, `typesense`). Ele é responsável por: + +- **Coleta**: Scrapers para gov.br e EBC +- **Armazenamento**: Gerenciamento do PostgreSQL (fonte de verdade) e HuggingFace (distribuição) +- **Enriquecimento**: Integração com Cogfy para classificação temática e sumários +- **Embeddings**: Geração de vetores para busca semântica +- **Indexação**: Sincronização com Typesense + +## Arquitetura + +```mermaid +graph TB + subgraph "Coleta" + S1[Scraper Gov.br] + S2[Scraper EBC] + end + + subgraph "Armazenamento" + PG[(PostgreSQL
Fonte de Verdade)] + HF[(HuggingFace
Dados Abertos)] + end + + subgraph "Processamento" + COG[Cogfy
Enriquecimento] + EMB[Embeddings API
Vetores 768-dim] + end + + subgraph "Indexação" + TS[Typesense
Busca] + end + + S1 --> PG + S2 --> PG + PG --> COG + COG --> PG + PG --> EMB + EMB --> PG + PG --> TS + PG -->|DAG Airflow| HF +``` + +## Estrutura do Repositório + +``` +data-platform/ +├── src/data_platform/ +│ ├── managers/ # Gerenciadores de storage +│ │ ├── postgres_manager.py # Acesso ao PostgreSQL +│ │ ├── dataset_manager.py # Acesso ao HuggingFace +│ │ └── storage_adapter.py # Abstração dual-write +│ ├── scrapers/ # Scrapers de notícias +│ │ ├── scrape_manager.py # Gov.br +│ │ └── ebc_scrape_manager.py +│ ├── cogfy/ # Integração Cogfy +│ │ ├── cogfy_manager.py +│ │ ├── upload_manager.py +│ │ └── enrichment_manager.py +│ ├── typesense/ # Módulo Typesense +│ │ ├── client.py +│ │ ├── collection.py +│ │ └── indexer.py +│ ├── jobs/ # Jobs de processamento +│ │ ├── typesense/sync_job.py +│ │ ├── embeddings/embedding_generator.py +│ │ └── hf_sync/ +│ ├── models/ # Modelos Pydantic +│ │ └── news.py +│ ├── dags/ # DAGs Airflow +│ │ └── sync_postgres_to_huggingface.py +│ └── cli.py # Interface de linha de comando +├── tests/ +├── scripts/ +└── pyproject.toml +``` + +## CLI - Comandos Disponíveis + +### Scraping + +```bash +# Raspar sites gov.br +data-platform scrape --start-date 2025-01-01 --end-date 2025-01-31 + +# Raspar sites EBC (Agência Brasil, TV Brasil) +data-platform scrape-ebc --start-date 2025-01-01 +``` + +### Enriquecimento + +```bash +# Upload para Cogfy +data-platform upload-cogfy --start-date 2025-01-01 + +# Buscar enriquecimento do Cogfy +data-platform enrich --start-date 2025-01-01 --force +``` + +### Embeddings + +```bash +# Gerar embeddings para notícias +data-platform generate-embeddings --start-date 2025-01-01 +``` + +### Typesense + +```bash +# Sincronizar PostgreSQL → Typesense +data-platform sync-typesense --start-date 2025-01-01 + +# Sincronização completa (full reload) +data-platform sync-typesense --full-sync + +# Listar collections +data-platform typesense-list + +# Deletar collection +data-platform typesense-delete --confirm +``` + +### HuggingFace + +```bash +# Sincronizar PostgreSQL → HuggingFace +data-platform sync-hf --start-date 2025-01-01 +``` + +## Storage Adapter + +O **StorageAdapter** é a camada de abstração que permite transição gradual entre backends de armazenamento. + +### Modos de Operação + +| Modo | Descrição | +|------|-----------| +| `POSTGRES` | Escreve apenas no PostgreSQL | +| `HUGGINGFACE` | Escreve apenas no HuggingFace (legado) | +| `DUAL_WRITE` | Escreve em ambos para transição segura | + +### Configuração + +```bash +# Variáveis de ambiente +STORAGE_BACKEND=postgres # postgres | huggingface | dual_write +STORAGE_READ_FROM=postgres # De onde ler os dados +``` + +### Uso no Código + +```python +from data_platform.managers.storage_adapter import StorageAdapter, StorageBackend + +adapter = StorageAdapter( + backend=StorageBackend.DUAL_WRITE, + read_from=StorageBackend.POSTGRES +) + +# Insert transparente - escreve em ambos +inserted = adapter.insert(news_list, allow_update=False) + +# Read - sempre do backend configurado em read_from +news = adapter.get(filters={"agency_key": "mec"}, limit=100) +``` + +## PostgresManager + +Gerenciador de acesso ao PostgreSQL com connection pooling e cache. + +### Características + +- **Connection Pooling**: Min 1, Max 10 conexões +- **Cache em Memória**: Agências e temas carregados na inicialização +- **Batch Operations**: Inserts otimizados com `execute_values()` +- **Conflict Handling**: Suporte a `ON CONFLICT DO UPDATE` + +### Exemplo de Uso + +```python +from data_platform.managers.postgres_manager import PostgresManager + +pm = PostgresManager() +pm.load_cache() # Carrega agências/temas em memória + +# Inserir notícias +news_list = [NewsInsert(...), ...] +inserted = pm.insert(news_list, allow_update=True) + +# Buscar notícias +news = pm.get( + filters={"agency_key": "mec"}, + limit=100, + order_by="published_at DESC" +) + +pm.close_all() +``` + +## Variáveis de Ambiente + +```bash +# PostgreSQL +DATABASE_URL=postgresql://user:pass@host:5432/govbrnews + +# HuggingFace +HF_TOKEN=hf_xxx +HF_REPO_ID=destaquesgovbr/govbrnews + +# Typesense +TYPESENSE_HOST=34.39.186.38 +TYPESENSE_PORT=8108 +TYPESENSE_API_KEY=xxx + +# Cogfy +COGFY_API_KEY=xxx +COGFY_COLLECTION_ID=xxx + +# Embeddings +EMBEDDINGS_API_URL=https://embeddings-api-xxx.run.app +EMBEDDINGS_API_KEY=xxx + +# Storage +STORAGE_BACKEND=postgres +STORAGE_READ_FROM=postgres +``` + +## Workflows GitHub Actions + +| Workflow | Trigger | Descrição | +|----------|---------|-----------| +| `main-workflow.yaml` | Diário (4AM UTC) | Pipeline completo: scrape → enrich → embed → sync | +| `typesense-maintenance-sync.yaml` | Diário (10AM UTC) | Sync incremental Typesense | +| `composer-deploy-dags.yaml` | Push | Deploy de DAGs no Airflow | + +## Instalação e Desenvolvimento + +```bash +# Clonar +git clone https://github.com/destaquesgovbr/data-platform.git +cd data-platform + +# Instalar com Poetry +poetry install + +# Rodar testes +poetry run pytest + +# Rodar linters +poetry run black src/ tests/ +poetry run ruff check src/ tests/ +poetry run mypy src/ +``` + +## Documentação Adicional + +O repositório possui documentação interna em `docs/`: + +- `docs/architecture/overview.md` - Arquitetura do sistema +- `docs/database/schema.md` - Schema PostgreSQL +- `docs/typesense/` - Documentação do Typesense +- `docs/development/setup.md` - Setup de desenvolvimento + +## Recursos Externos + +- **HuggingFace Dataset**: [nitaibezerra/govbrnews](https://huggingface.co/datasets/nitaibezerra/govbrnews) +- **Portal**: [destaques.gov.br](https://destaques.gov.br) diff --git a/docs/workflows/airflow-dags.md b/docs/workflows/airflow-dags.md new file mode 100644 index 0000000..64206c2 --- /dev/null +++ b/docs/workflows/airflow-dags.md @@ -0,0 +1,315 @@ +# Airflow DAGs (Cloud Composer) + +O projeto utiliza **Cloud Composer 3** (Apache Airflow gerenciado) para orquestração de pipelines de dados, especialmente a sincronização entre PostgreSQL e HuggingFace. + +!!! info "Cloud Composer" + **Ambiente**: `destaquesgovbr-composer` + **Região**: `us-central1` + **Versão**: Composer 3 / Airflow 3.x + +## Arquitetura + +```mermaid +graph TB + subgraph "Cloud Composer" + SCHED[Scheduler] + WORKER[Workers
1-3 instâncias] + UI[Web UI] + TRIGGER[Triggerer] + end + + subgraph "DAGs" + DAG1[sync_postgres_to_huggingface] + DAG2[test_postgres_connection] + end + + subgraph "Recursos GCP" + PG[(PostgreSQL
Cloud SQL)] + SM[Secret Manager] + GCS[GCS Bucket
DAGs] + end + + subgraph "Externos" + HF[(HuggingFace)] + end + + GCS --> SCHED + SCHED --> WORKER + WORKER --> DAG1 + WORKER --> DAG2 + DAG1 --> PG + DAG1 --> HF + SM --> WORKER +``` + +## DAGs Disponíveis + +### `sync_postgres_to_huggingface` + +Sincroniza notícias do PostgreSQL para o HuggingFace diariamente. + +| Configuração | Valor | +|--------------|-------| +| **Schedule** | `0 6 * * *` (6 AM UTC) | +| **Catchup** | Desabilitado | +| **Retries** | 3 (com backoff exponencial) | +| **Tags** | `sync`, `huggingface`, `postgres`, `daily` | + +#### Fluxo de Execução + +```mermaid +sequenceDiagram + participant A as Airflow + participant PG as PostgreSQL + participant API as HuggingFace API + participant HF as HuggingFace Dataset + + A->>PG: Query notícias do dia anterior + PG-->>A: Registros (ex: 500) + + A->>API: Consulta IDs existentes via Dataset Viewer + API-->>A: IDs já sincronizados (ex: 480) + + Note over A: Filtra apenas novos (ex: 20) + + A->>A: Cria parquet shard + A->>HF: Upload do shard + HF-->>A: Commit confirmado + + A->>HF: Upload para dataset reduzido +``` + +#### Abordagem Incremental + +A DAG utiliza uma abordagem de **append incremental via parquet shards** para evitar problemas de memória: + +1. **Consulta IDs existentes** via Dataset Viewer API (sem baixar o dataset completo) +2. **Cria parquet shard** apenas com novos registros +3. **Upload direto** via `huggingface_hub` + +**Vantagens**: +- Memória: ~10MB (apenas novos registros) vs ~1-2GB (dataset completo) +- Deduplicação automática +- Commits atômicos por dia + +#### Estrutura do Shard + +``` +data/train-{YYYY-MM-DD}-{HHMMSS}.parquet +``` + +Exemplo: `data/train-2025-01-10-060532.parquet` + +#### Colunas Sincronizadas + +```python +HF_COLUMNS = [ + "unique_id", "agency", "published_at", "updated_datetime", "extracted_at", + "title", "subtitle", "editorial_lead", "url", "content", + "image", "video_url", "category", "tags", + "theme_1_level_1", "theme_1_level_1_code", "theme_1_level_1_label", + "theme_1_level_2_code", "theme_1_level_2_label", + "theme_1_level_3_code", "theme_1_level_3_label", + "most_specific_theme_code", "most_specific_theme_label", + "summary" +] +``` + +#### Datasets Atualizados + +| Dataset | Colunas | Uso | +|---------|---------|-----| +| `nitaibezerra/govbrnews` | Todas (24) | Análise completa | +| `nitaibezerra/govbrnews-reduced` | 4 (published_at, agency, title, url) | Listagens rápidas | + +### `test_postgres_connection` + +DAG de teste para verificar conectividade com o PostgreSQL. + +| Configuração | Valor | +|--------------|-------| +| **Schedule** | Manual (`None`) | +| **Uso** | Validação pós-deploy | + +## Configuração do Composer + +### Workloads + +| Componente | CPU | Memória | Storage | Instâncias | +|------------|-----|---------|---------|------------| +| Scheduler | 0.5 | 2GB | 2GB | 1 | +| Web Server | 1 | 2GB | 2GB | 1 | +| Worker | 1 | 2GB | 2GB | 1-3 (auto) | +| Triggerer | 0.5 | 2GB | - | 1 | +| DAG Processor | 0.5 | 2GB | 1GB | 1 | + +### Airflow Config Overrides + +```python +{ + # Secret Manager Backend + "secrets-backend": "airflow.providers.google.cloud.secrets.secret_manager.CloudSecretManagerBackend", + "secrets-backend_kwargs": { + "connections_prefix": "airflow-connections", + "variables_prefix": "airflow-variables", + "project_id": "inspire-7-finep" + }, + + # Timezone + "core-default_timezone": "America/Sao_Paulo", + + # Web UI + "webserver-rbac": "True", + "webserver-authenticate": "True", +} +``` + +### PyPI Packages + +``` +psycopg2-binary>=2.9.9 +apache-airflow-providers-postgres>=5.10.2 +apache-airflow-providers-google>=10.14.0 +sqlalchemy>=1.4.52 +requests>=2.31.0 +pandas>=2.0.0 +``` + +### Environment Variables + +```bash +POSTGRES_HOST=10.x.x.x # IP privado Cloud SQL +POSTGRES_PORT=5432 +POSTGRES_DB=govbrnews +GCP_PROJECT_ID=inspire-7-finep +GCP_REGION=southamerica-east1 +TYPESENSE_HOST=34.39.186.38 +``` + +## Connections + +As connections são gerenciadas via **Secret Manager**: + +| Connection ID | Tipo | Secret | +|--------------|------|--------| +| `postgres_default` | Postgres | `airflow-connections-postgres_default` | +| `huggingface_default` | Generic | `airflow-connections-huggingface_default` | + +### Formato das Connections + +```json +// postgres_default +{ + "conn_type": "postgres", + "host": "10.x.x.x", + "port": 5432, + "schema": "govbrnews", + "login": "govbrnews_app", + "password": "xxx" +} + +// huggingface_default +{ + "conn_type": "generic", + "password": "hf_xxx" // Token HuggingFace +} +``` + +## Deploy de DAGs + +### Via GitHub Actions + +O workflow `composer-deploy-dags.yaml` faz deploy automático em push: + +```yaml +# .github/workflows/composer-deploy-dags.yaml +on: + push: + paths: + - 'src/data_platform/dags/**' + +jobs: + deploy: + steps: + - name: Upload DAGs to GCS + run: | + gsutil -m cp -r src/data_platform/dags/* \ + gs://${{ env.COMPOSER_BUCKET }}/dags/ +``` + +### Manual via gcloud + +```bash +# Descobrir bucket do Composer +BUCKET=$(gcloud composer environments describe destaquesgovbr-composer \ + --location us-central1 \ + --format="value(config.dagGcsPrefix)") + +# Upload das DAGs +gsutil -m cp -r src/data_platform/dags/* $BUCKET/ +``` + +## Monitoramento + +### Acessar Web UI + +```bash +# Obter URL do Airflow +gcloud composer environments describe destaquesgovbr-composer \ + --location us-central1 \ + --format="value(config.airflowUri)" +``` + +### Logs + +```bash +# Ver logs de uma DAG run +gcloud composer environments run destaquesgovbr-composer \ + --location us-central1 \ + dags list-runs -- -d sync_postgres_to_huggingface +``` + +### Métricas + +O Composer exporta métricas para Cloud Monitoring: + +- `composer.googleapis.com/environment/dag_processing/total_parse_time` +- `composer.googleapis.com/environment/worker/task_success_count` +- `composer.googleapis.com/environment/worker/task_failed_count` + +## Troubleshooting + +### DAG não aparece na UI + +1. Verificar se o arquivo foi copiado para o bucket: + ```bash + gsutil ls $BUCKET/dags/ + ``` + +2. Verificar logs do DAG Processor: + ```bash + gcloud composer environments run destaquesgovbr-composer \ + --location us-central1 \ + tasks log-read -- -d sync_postgres_to_huggingface -t sync_news_to_huggingface + ``` + +### Erro de conexão com PostgreSQL + +1. Verificar se a connection existe no Secret Manager +2. Verificar se o Composer tem acesso ao Cloud SQL via VPC + +### Erro de memória (OOM) + +- A abordagem incremental resolve isso +- Se persistir, aumentar `memory_gb` dos workers no Terraform + +## Custos Estimados + +| Componente | Custo/mês | +|------------|-----------| +| Cloud Composer (SMALL) | ~$100-150 | +| GCS (DAGs bucket) | ~$1 | +| **Total** | **~$100-150** | + +!!! tip "Otimização de Custos" + O Composer está em `us-central1` (não `southamerica-east1`) para reduzir custos. A latência adicional é aceitável para jobs batch. diff --git a/mkdocs.yml b/mkdocs.yml index 33f35f8..f7456d9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,9 +44,11 @@ nav: - Home: index.md - Arquitetura: - Visão Geral: arquitetura/visao-geral.md + - PostgreSQL: arquitetura/postgresql.md - Fluxo de Dados: arquitetura/fluxo-de-dados.md - Componentes: arquitetura/componentes-estruturantes.md - Módulos: + - Data Platform: modulos/data-platform.md - Scraper: modulos/scraper.md - Portal: modulos/portal.md - Cogfy/LLM: modulos/cogfy-integracao.md @@ -59,6 +61,7 @@ nav: - Deploy Portal: workflows/portal-deploy.md - Docker Builds: workflows/docker-builds.md - Typesense Data: workflows/typesense-data.md + - Airflow DAGs: workflows/airflow-dags.md - Infraestrutura: - Arquitetura GCP: infraestrutura/arquitetura-gcp.md - Terraform: infraestrutura/terraform-guide.md From 9dfbacdc27d697b17667c37961b0a6e31f1c9ac4 Mon Sep 17 00:00:00 2001 From: nitai Date: Tue, 13 Jan 2026 10:08:21 -0300 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20atualiza=20m=C3=B3dulos=20e=20workf?= =?UTF-8?q?lows=20para=20nova=20arquitetura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplifica scraper.md com redirect para data-platform - Atualiza typesense-local.md para usar PostgreSQL como fonte - Atualiza cogfy-integracao.md com fluxo PostgreSQL - Atualiza scraper-pipeline.md com 7 jobs (inclui embeddings) - Atualiza typesense-data.md para sincronizar do PostgreSQL - Atualiza arquitetura-gcp.md com Cloud SQL, Composer, Embeddings API Co-Authored-By: Claude Opus 4.5 --- docs/infraestrutura/arquitetura-gcp.md | 277 ++++++++++++--- docs/modulos/cogfy-integracao.md | 185 ++++++---- docs/modulos/scraper.md | 461 ++----------------------- docs/modulos/typesense-local.md | 236 ++++++------- docs/workflows/scraper-pipeline.md | 255 +++++++++----- docs/workflows/typesense-data.md | 252 ++++++++------ 6 files changed, 800 insertions(+), 866 deletions(-) diff --git a/docs/infraestrutura/arquitetura-gcp.md b/docs/infraestrutura/arquitetura-gcp.md index 63f1c44..0172430 100644 --- a/docs/infraestrutura/arquitetura-gcp.md +++ b/docs/infraestrutura/arquitetura-gcp.md @@ -13,15 +13,24 @@ flowchart TB subgraph "Google Cloud Platform" subgraph "Networking" VPC[VPC Network] - SUB[Subnet us-east1] + SUB[Subnet southamerica-east1] FW[Firewall Rules] end + subgraph "Databases" + PG[(Cloud SQL
PostgreSQL 15)] + end + subgraph "Compute" CR[Cloud Run
Portal Next.js] + EMB[Cloud Run
Embeddings API] CE[Compute Engine
Typesense] end + subgraph "Orchestration" + CC[Cloud Composer 3
Airflow 3.x] + end + subgraph "Storage & Registry" AR[Artifact Registry
Docker Images] SM[Secret Manager
Credentials] @@ -36,22 +45,66 @@ flowchart TB GH[GitHub Actions] -->|OIDC| WIF WIF -->|Impersonate| SA SA -->|Deploy| CR + SA -->|Deploy| EMB SA -->|Push| AR CR -->|VPC Connector| VPC + EMB -->|VPC Connector| VPC + CC -->|VPC| VPC VPC --> CE + VPC --> PG ``` --- ## Componentes -### 1. Cloud Run (Portal) +### 1. Cloud SQL (PostgreSQL) - Fonte de Verdade + +| Propriedade | Valor | +|-------------|-------| +| Instância | `destaquesgovbr-postgres` | +| Versão | PostgreSQL 15 | +| Região | `southamerica-east1` | +| Tier | `db-f1-micro` (upgrade planejado) | +| vCPU | 1 | +| Memória | 3.75 GB | +| Disco | 50GB SSD (auto-resize até 500GB) | +| Backup | Diário com PITR 7 dias | + +**Características:** +- Fonte de verdade central do sistema +- Private IP (acesso apenas via VPC) +- Backups automáticos diários +- Point-in-time recovery (7 dias) + +→ Veja detalhes em [PostgreSQL](../arquitetura/postgresql.md) + +### 2. Cloud Composer (Airflow) + +| Propriedade | Valor | +|-------------|-------| +| Ambiente | `destaquesgovbr-composer` | +| Versão | Composer 3 / Airflow 3.x | +| Região | `southamerica-east1` | +| Tipo | Small | + +**DAGs:** +- `sync_postgres_to_huggingface` - Sync diário para HuggingFace (6AM UTC) + +**Características:** +- Orquestração de pipelines de dados +- Monitoramento e alertas integrados +- Retry automático de tarefas + +→ Veja detalhes em [Airflow DAGs](../workflows/airflow-dags.md) + +### 3. Cloud Run (Portal) | Propriedade | Valor | |-------------|-------| | Serviço | `portal` | -| Região | `us-east1` | +| Região | `southamerica-east1` | | CPU | 1 | | Memória | 512Mi | | Min instances | 0 | @@ -63,50 +116,77 @@ flowchart TB - VPC Connector para acesso ao Typesense - HTTPS automático -### 2. Compute Engine (Typesense) +### 4. Cloud Run (Embeddings API) + +| Propriedade | Valor | +|-------------|-------| +| Serviço | `embeddings-api` | +| Região | `southamerica-east1` | +| CPU | 2 | +| Memória | 4Gi | +| Min instances | 0 | +| Max instances | 5 | + +**Características:** +- Modelo: `paraphrase-multilingual-mpnet-base-v2` +- Vetores 768-dim +- VPC Connector para acesso ao PostgreSQL +- GPU opcional para maior throughput + +### 5. Compute Engine (Typesense) | Propriedade | Valor | |-------------|-------| | Nome | `typesense-server` | | Tipo | `e2-medium` | -| Região | `us-east1-b` | +| Região | `southamerica-east1-a` | | Disco | 50GB SSD | | IP | Interno (VPC) | **Características:** - VM dedicada para Typesense +- Suporte a busca vetorial (768-dim) - Persistência de dados em disco - Acesso via VPC (não exposto à internet) -### 3. Artifact Registry +### 6. Artifact Registry | Propriedade | Valor | |-------------|-------| | Repositório | `destaquesgovbr` | -| Região | `us-east1` | +| Região | `southamerica-east1` | | Formato | Docker | **Imagens:** - `portal` - Imagem do portal Next.js +- `data-platform` - Imagem do data platform +- `embeddings-api` - Imagem da API de embeddings -### 4. Secret Manager +### 7. Secret Manager Secrets armazenadas: -- `typesense-api-key` - API Key do Typesense -- Outras credenciais sensíveis +| Secret | Descrição | +|--------|-----------| +| `postgres-host` | IP privado do Cloud SQL | +| `postgres-password` | Senha do banco | +| `typesense-api-key` | API Key do Typesense | +| `cogfy-api-key` | API Key do Cogfy | +| `cogfy-collection-id` | ID da collection Cogfy | +| `hf-token` | Token HuggingFace (write) | -### 5. VPC Network +### 8. VPC Network | Propriedade | Valor | |-------------|-------| | Nome | `destaquesgovbr-vpc` | | Subnet | `10.0.0.0/24` | -| Região | `us-east1` | +| Região | `southamerica-east1` | **Firewall Rules:** - SSH interno - Typesense (8108) interno +- PostgreSQL (5432) interno - HTTPS (443) externo para Cloud Run --- @@ -117,19 +197,27 @@ Secrets armazenadas: flowchart LR subgraph "Internet" U[Usuários] + HF[(HuggingFace)] end - subgraph "GCP - us-east1" + subgraph "GCP - southamerica-east1" subgraph "VPC 10.0.0.0/24" CR[Cloud Run
Portal] + EMB[Cloud Run
Embeddings API] VC[VPC Connector] TS[Compute Engine
Typesense
10.0.0.x] + PG[(Cloud SQL
PostgreSQL
10.0.0.y)] + CC[Cloud Composer
Airflow] end end U -->|HTTPS| CR CR -->|VPC Connector| VC VC -->|:8108| TS + VC -->|:5432| PG + EMB -->|VPC Connector| VC + CC -->|:5432| PG + CC -->|Sync| HF ``` --- @@ -138,11 +226,15 @@ flowchart LR | Componente | Custo/mês | |------------|-----------| -| Compute Engine (Typesense) | ~$55 | -| Cloud Run (Portal) | ~$12-17 | +| Cloud SQL (PostgreSQL) | ~$48 | +| Cloud Composer (Airflow) | ~$100-150 | +| Compute Engine (Typesense) | ~$64 | +| Cloud Run (Portal) | ~$10 | +| Cloud Run (Embeddings API) | ~$5 | | Artifact Registry | ~$1 | | VPC Connector | ~$2 | -| **Total** | **~$70-75** | +| Secret Manager | ~$1 | +| **Total** | **~$230-280** | > Valores aproximados. Podem variar com uso. @@ -153,15 +245,17 @@ flowchart LR | Recurso | Região/Zona | |---------|-------------| | VPC | global | -| Subnet | us-east1 | -| Cloud Run | us-east1 | -| Compute Engine | us-east1-b | -| Artifact Registry | us-east1 | - -**Por que us-east1?** -- Menor latência para Brasil +| Subnet | southamerica-east1 | +| Cloud SQL | southamerica-east1 | +| Cloud Run | southamerica-east1 | +| Cloud Composer | southamerica-east1 | +| Compute Engine | southamerica-east1-a | +| Artifact Registry | southamerica-east1 | + +**Por que southamerica-east1 (São Paulo)?** +- Menor latência para usuários brasileiros +- Conformidade com dados do governo - Disponibilidade de recursos -- Custo competitivo --- @@ -190,7 +284,7 @@ sequenceDiagram ```bash # Via gcloud -gcloud compute ssh typesense-server --zone=us-east1-b +gcloud compute ssh typesense-server --zone=southamerica-east1-a # Verificar Typesense curl http://localhost:8108/health @@ -198,65 +292,133 @@ curl http://localhost:8108/health --- +## Acesso ao PostgreSQL + +```bash +# Via Cloud SQL Proxy (desenvolvimento local) +cloud_sql_proxy -instances=PROJECT_ID:southamerica-east1:destaquesgovbr-postgres=tcp:5432 + +# Conectar +psql -h localhost -U admin -d destaquesgovbr + +# Via gcloud (admin) +gcloud sql connect destaquesgovbr-postgres --user=admin +``` + +--- + +## Acesso ao Composer (Airflow) + +```bash +# Abrir UI do Airflow +gcloud composer environments describe destaquesgovbr-composer \ + --location=southamerica-east1 \ + --format='value(config.airflowUri)' + +# Listar DAGs +gcloud composer environments run destaquesgovbr-composer \ + --location=southamerica-east1 \ + dags list + +# Trigger manual de DAG +gcloud composer environments run destaquesgovbr-composer \ + --location=southamerica-east1 \ + dags trigger -- sync_postgres_to_huggingface +``` + +--- + ## Monitoramento +### Cloud SQL + +```bash +# Status da instância +gcloud sql instances describe destaquesgovbr-postgres + +# Métricas (via Console) +# Console > SQL > destaquesgovbr-postgres > Monitoring +``` + ### Cloud Run ```bash # Status do serviço -gcloud run services describe portal --region=us-east1 +gcloud run services describe portal --region=southamerica-east1 # Logs -gcloud run services logs read portal --region=us-east1 +gcloud run services logs read portal --region=southamerica-east1 +``` -# Métricas (via Console) -# Console > Cloud Run > portal > Metrics +### Cloud Composer + +```bash +# Status do ambiente +gcloud composer environments describe destaquesgovbr-composer \ + --location=southamerica-east1 + +# Logs (via Console) +# Console > Composer > destaquesgovbr-composer > Logs ``` ### Compute Engine ```bash # Status da VM -gcloud compute instances describe typesense-server --zone=us-east1-b +gcloud compute instances describe typesense-server --zone=southamerica-east1-a # Logs do sistema -gcloud compute ssh typesense-server --zone=us-east1-b -- sudo journalctl -u typesense - -# Métricas (via Console) -# Console > Compute Engine > typesense-server > Monitoring +gcloud compute ssh typesense-server --zone=southamerica-east1-a -- sudo journalctl -u typesense ``` --- ## Backup e Recuperação +### PostgreSQL (Cloud SQL) + +```bash +# Backups são automáticos (diários) +# Listar backups +gcloud sql backups list --instance=destaquesgovbr-postgres + +# Restaurar de um backup +gcloud sql backups restore BACKUP_ID \ + --restore-instance=destaquesgovbr-postgres \ + --backup-instance=destaquesgovbr-postgres +``` + ### Typesense Data ```bash # SSH no servidor -gcloud compute ssh typesense-server --zone=us-east1-b +gcloud compute ssh typesense-server --zone=southamerica-east1-a # Backup do diretório de dados sudo tar -czvf /tmp/typesense-backup.tar.gz /var/lib/typesense/data # Download do backup -gcloud compute scp typesense-server:/tmp/typesense-backup.tar.gz . --zone=us-east1-b +gcloud compute scp typesense-server:/tmp/typesense-backup.tar.gz . --zone=southamerica-east1-a ``` -### Recuperação - -1. Parar Typesense -2. Restaurar dados do backup -3. Reiniciar Typesense -4. Verificar integridade - --- ## Escalabilidade -### Cloud Run (Portal) +### Cloud SQL + +- **Vertical**: Upgrade de tier (db-f1-micro → db-g1-small → db-custom) +- **Read replicas**: Disponível se necessário +- **Disk auto-resize**: Habilitado (até 500GB) + +### Cloud Composer + +- **Horizontal**: Adicionar workers +- **Vertical**: Upgrade de tipo de ambiente -- **Automático**: Escala de 0 a 10 instâncias +### Cloud Run + +- **Automático**: Escala de 0 a N instâncias - **Cold start**: ~2-3 segundos - **Ajustes**: Via Terraform ou Console @@ -273,6 +435,7 @@ gcloud compute scp typesense-server:/tmp/typesense-backup.tar.gz . --zone=us-eas ### Rede - Typesense **não exposto** à internet +- PostgreSQL **não exposto** à internet - Acesso apenas via VPC Connector - Firewall restritivo @@ -288,11 +451,35 @@ gcloud compute scp typesense-server:/tmp/typesense-backup.tar.gz . --zone=us-eas - Versionamento automático - Acesso auditado +### Dados + +- Backups automáticos do Cloud SQL +- Criptografia em repouso (padrão GCP) +- Criptografia em trânsito (TLS) + +--- + +## Terraform (Arquivos Principais) + +| Arquivo | Conteúdo | +|---------|----------| +| `main.tf` | Providers e configurações globais | +| `network.tf` | VPC, subnets, firewall | +| `cloud_sql.tf` | PostgreSQL | +| `composer.tf` | Cloud Composer | +| `cloud_run.tf` | Portal e Embeddings API | +| `compute.tf` | Typesense VM | +| `iam.tf` | Service accounts e Workload Identity | +| `secrets.tf` | Secret Manager | +| `artifact_registry.tf` | Registry Docker | + --- ## Links Relacionados +- [PostgreSQL](../arquitetura/postgresql.md) - Banco de dados central - [Terraform Guide](./terraform-guide.md) - Como gerenciar a infraestrutura - [Secrets e IAM](./secrets-iam.md) - Permissões e credenciais - [Deploy Portal](../workflows/portal-deploy.md) - Workflow de deploy - [Typesense Data](../workflows/typesense-data.md) - Carga de dados +- [Airflow DAGs](../workflows/airflow-dags.md) - Pipelines de orquestração diff --git a/docs/modulos/cogfy-integracao.md b/docs/modulos/cogfy-integracao.md index 011353e..7396c2e 100644 --- a/docs/modulos/cogfy-integracao.md +++ b/docs/modulos/cogfy-integracao.md @@ -11,12 +11,12 @@ O **Cogfy** é uma plataforma SaaS que fornece inferência LLM para: ```mermaid flowchart LR - SC[Scraper] -->|Upload| CF[Cogfy API] + PG[(PostgreSQL)] -->|Upload| CF[Cogfy API] CF -->|LLM Inference| CL[Classificação] CF -->|LLM Inference| SU[Sumarização] CL --> EN[EnrichmentManager] SU --> EN - EN -->|Update| HF[(HuggingFace)] + EN -->|Update| PG ``` --- @@ -26,11 +26,11 @@ flowchart LR ### 1. Upload para Cogfy ```python -# upload_to_cogfy_manager.py +# src/data_platform/cogfy/upload_manager.py def upload_to_cogfy(start_date: date, end_date: date): """Envia notícias para processamento no Cogfy.""" - # 1. Carregar artigos do HuggingFace - df = dataset_manager.load_by_date_range(start_date, end_date) + # 1. Carregar artigos do PostgreSQL + articles = postgres_manager.get_news_by_date_range(start_date, end_date) # 2. Converter para formato Cogfy records = [ @@ -39,7 +39,7 @@ def upload_to_cogfy(start_date: date, end_date: date): "title": row["title"], "content": row["content"][:5000], # Limite de caracteres } - for _, row in df.iterrows() + for row in articles ] # 3. Enviar em batches @@ -56,20 +56,36 @@ O Cogfy processa via LLM, o que leva aproximadamente **20 minutos** para um batc run: sleep 1200 # 20 minutos ``` -### 3. Buscar Resultados +### 3. Buscar Resultados e Atualizar PostgreSQL ```python -# enrichment_manager.py -def fetch_enrichment(unique_id: str) -> dict: - """Busca resultados processados do Cogfy.""" - record = cogfy_manager.get_record(unique_id) - - return { - "theme_1_level_1": record.get("theme_1_level_1"), - "theme_1_level_2": record.get("theme_1_level_2"), - "theme_1_level_3": record.get("theme_1_level_3"), - "summary": record.get("summary"), - } +# src/data_platform/cogfy/enrichment_manager.py +def enrich(start_date: date, end_date: date) -> int: + """Busca resultados do Cogfy e atualiza PostgreSQL.""" + # 1. Buscar artigos sem enriquecimento + articles = postgres_manager.get_news_without_enrichment(start_date, end_date) + + # 2. Para cada artigo, buscar no Cogfy + for article in articles: + cogfy_data = cogfy_manager.get_record(article["unique_id"]) + + # 3. Mapear códigos para labels + theme_l1_code, theme_l1_label = parse_theme(cogfy_data.get("theme_1_level_1")) + theme_l2_code, theme_l2_label = parse_theme(cogfy_data.get("theme_1_level_2")) + theme_l3_code, theme_l3_label = parse_theme(cogfy_data.get("theme_1_level_3")) + + # 4. Calcular most_specific_theme (L3 > L2 > L1) + most_specific = theme_l3_code or theme_l2_code or theme_l1_code + + # 5. Atualizar PostgreSQL + postgres_manager.update_enrichment( + unique_id=article["unique_id"], + theme_l1_id=get_theme_id(theme_l1_code), + theme_l2_id=get_theme_id(theme_l2_code), + theme_l3_id=get_theme_id(theme_l3_code), + most_specific_theme_id=get_theme_id(most_specific), + summary=cogfy_data.get("summary") + ) ``` --- @@ -110,6 +126,30 @@ class CogfyManager: --- +## CLI do data-platform + +```bash +# Upload para Cogfy +data-platform upload-cogfy --start-date YYYY-MM-DD --end-date YYYY-MM-DD + +# Buscar enriquecimento (após ~20 min) +data-platform enrich --start-date YYYY-MM-DD --end-date YYYY-MM-DD +``` + +--- + +## Arquivos Principais + +| Componente | Localização | +|------------|-------------| +| CogfyManager | `src/data_platform/cogfy/cogfy_manager.py` | +| UploadManager | `src/data_platform/cogfy/upload_manager.py` | +| EnrichmentManager | `src/data_platform/cogfy/enrichment_manager.py` | +| ThemeMapper | `src/data_platform/enrichment/theme_mapper.py` | +| themes_tree.yaml | `src/data_platform/enrichment/themes_tree.yaml` | + +--- + ## Configuração no Cogfy > **Nota**: A configuração do Cogfy é feita via interface web. Screenshots serão adicionados futuramente. @@ -156,21 +196,21 @@ Resumo: ## Mapeamento de Campos -### Entrada (Scraper → Cogfy) +### Entrada (PostgreSQL → Cogfy) -| Campo Scraper | Campo Cogfy | Tipo | -|---------------|-------------|------| +| Campo PostgreSQL | Campo Cogfy | Tipo | +|------------------|-------------|------| | unique_id | unique_id | string | | title | title | string | | content | content | string (max 5000 chars) | -### Saída (Cogfy → Dataset) +### Saída (Cogfy → PostgreSQL) -| Campo Cogfy | Campo Dataset | Tipo | -|-------------|---------------|------| -| theme_1_level_1 | theme_1_level_1_code + label | string → split | -| theme_1_level_2 | theme_1_level_2_code + label | string → split | -| theme_1_level_3 | theme_1_level_3_code + label | string → split | +| Campo Cogfy | Campo PostgreSQL | Tipo | +|-------------|------------------|------| +| theme_1_level_1 | theme_l1_id (FK) | string → ID lookup | +| theme_1_level_2 | theme_l2_id (FK) | string → ID lookup | +| theme_1_level_3 | theme_l3_id (FK) | string → ID lookup | | summary | summary | string | ### Processamento de Temas @@ -184,6 +224,12 @@ def parse_theme(theme_str: str) -> tuple[str, str]: return None, None parts = theme_str.split(" - ", 1) return parts[0].strip(), parts[1].strip() + +def get_theme_id(code: str) -> int | None: + """Busca ID do tema no PostgreSQL pelo código.""" + if not code: + return None + return postgres_manager.get_theme_id_by_code(code) ``` --- @@ -210,17 +256,18 @@ COGFY_COLLECTION_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ### Notícia não classificada ```python -def enrich_article(row: dict) -> dict: +def enrich_article(article: dict) -> bool: """Enriquece artigo com dados do Cogfy.""" try: - cogfy_data = cogfy_manager.get_record(row["unique_id"]) + cogfy_data = cogfy_manager.get_record(article["unique_id"]) if not cogfy_data: - logger.warning(f"No Cogfy data for {row['unique_id']}") - return row # Retorna sem enriquecimento + logger.warning(f"No Cogfy data for {article['unique_id']}") + return False # ... processar enriquecimento + return True except Exception as e: - logger.error(f"Error enriching {row['unique_id']}: {e}") - return row + logger.error(f"Error enriching {article['unique_id']}: {e}") + return False ``` ### Retry em falhas de API @@ -234,24 +281,52 @@ def get_record(self, unique_id: str) -> dict: --- +## Fluxo no Pipeline Diário + +```mermaid +sequenceDiagram + participant GH as GitHub Actions + participant PG as PostgreSQL + participant CF as Cogfy + participant EN as EnrichmentManager + + GH->>PG: get_news(date_range) + PG-->>GH: Registros + GH->>CF: Upload para inferência + Note over CF: Processa via LLM (~20 min) + CF-->>EN: Themes + Summary prontos + EN->>CF: GET records por unique_id + EN->>EN: Mapeia código → ID (tabela themes) + EN->>EN: Calcula most_specific_theme_id + EN->>PG: UPDATE news SET theme_l1_id, summary, ... +``` + +--- + ## Métricas e Monitoramento ### Taxa de classificação -```python -def calculate_classification_rate(df: pd.DataFrame) -> float: - """Calcula % de notícias classificadas.""" - classified = df["theme_1_level_1_code"].notna().sum() - total = len(df) - return classified / total * 100 +```sql +-- % de notícias classificadas +SELECT + COUNT(*) FILTER (WHERE theme_l1_id IS NOT NULL) * 100.0 / COUNT(*) as classification_rate +FROM news +WHERE published_at >= NOW() - INTERVAL '7 days'; ``` ### Distribuição por tema -```python -def theme_distribution(df: pd.DataFrame) -> dict: - """Retorna distribuição de notícias por tema.""" - return df["theme_1_level_1_label"].value_counts().to_dict() +```sql +-- Top 10 temas mais frequentes +SELECT + t.label, + COUNT(*) as count +FROM news n +JOIN themes t ON n.most_specific_theme_id = t.id +GROUP BY t.label +ORDER BY count DESC +LIMIT 10; ``` --- @@ -278,27 +353,6 @@ O Cogfy cobra por: --- -## Fluxo no Pipeline Diário - -```mermaid -sequenceDiagram - participant SC as Scraper - participant HF as HuggingFace - participant CF as Cogfy - participant EN as Enrichment - - SC->>HF: Insert novos artigos - SC->>CF: Upload para inferência - Note over CF: Processa via LLM (~20 min) - CF-->>EN: Themes + Summary prontos - EN->>CF: GET records por unique_id - EN->>EN: Mapeia código → label - EN->>EN: Calcula most_specific_theme - EN->>HF: Update com enriquecimento -``` - ---- - ## Placeholder para Screenshots > **TODO**: Adicionar screenshots da interface do Cogfy: @@ -312,7 +366,8 @@ sequenceDiagram ## Links Relacionados +- [Data Platform](data-platform.md) - Repositório unificado +- [PostgreSQL](../arquitetura/postgresql.md) - Fonte de verdade - [Fluxo de Dados](../arquitetura/fluxo-de-dados.md) - Pipeline completo - [Árvore Temática](./arvore-tematica.md) - Taxonomia usada -- [Módulo Scraper](./scraper.md) - Upload e enriquecimento - [Pipeline Scraper](../workflows/scraper-pipeline.md) - Workflow diário diff --git a/docs/modulos/scraper.md b/docs/modulos/scraper.md index 28dab87..cffeff3 100644 --- a/docs/modulos/scraper.md +++ b/docs/modulos/scraper.md @@ -1,443 +1,54 @@ -# Módulo: Scraper (scraper) +# Módulo: Scraper (Arquivado) -> Pipeline de coleta e enriquecimento de notícias governamentais. +!!! warning "Módulo Migrado" + Este módulo foi migrado para o repositório **data-platform**. + O repositório `scraper` foi arquivado. -**Repositório**: [github.com/destaquesgovbr/scraper](https://github.com/destaquesgovbr/scraper) - -## Visão Geral - -O scraper é responsável por: - -1. **Raspar** notícias de ~160+ sites gov.br e EBC -2. **Armazenar** no dataset HuggingFace -3. **Enviar** para classificação no Cogfy -4. **Enriquecer** com temas e resumos gerados por LLM - -```mermaid -flowchart LR - A[Sites gov.br] -->|Raspagem| B[WebScraper] - B -->|Insert| C[(HuggingFace)] - C -->|Upload| D[Cogfy] - D -->|Themes + Summary| E[EnrichmentManager] - E -->|Update| C -``` - ---- - -## Stack Tecnológico - -| Tecnologia | Versão | Uso | -|------------|--------|-----| -| Python | 3.12+ | Linguagem principal | -| Poetry | 1.7+ | Gerenciamento de dependências | -| BeautifulSoup4 | 4.x | Parsing HTML | -| datasets | HuggingFace | Gerenciamento de dados | -| requests | 2.x | Requisições HTTP | -| retry | - | Lógica de retry | - ---- - -## Estrutura do Repositório - -``` -scraper/ -├── src/ -│ ├── main.py # CLI principal (Typer) -│ ├── dataset_manager.py # Gerenciador HuggingFace -│ ├── cogfy_manager.py # Cliente API Cogfy -│ ├── upload_to_cogfy_manager.py # Upload para inferência -│ ├── enrichment_manager.py # Busca e aplica enriquecimento -│ ├── enrichment/ -│ │ ├── themes_tree.yaml # Árvore temática (25 temas) -│ │ └── theme_mapper.py # Mapeamento código → label -│ └── scraper/ -│ ├── webscraper.py # Scraper genérico gov.br -│ ├── ebc_webscraper.py # Scraper especializado EBC -│ ├── scrape_manager.py # Orquestração de scrapers -│ ├── agencies.yaml # Mapeamento ID → Nome -│ └── site_urls.yaml # URLs de raspagem (~160+) -├── tests/ # Testes unitários -├── .github/workflows/ -│ ├── main-workflow.yaml # Pipeline diário (4AM UTC) -│ ├── scraper-dispatch.yaml # Trigger manual -│ └── docker-build.yaml # Build de imagem -├── pyproject.toml # Dependências Poetry -├── Dockerfile # Build Docker -└── README.md -``` + **Novo repositório**: [github.com/destaquesgovbr/data-platform](https://github.com/destaquesgovbr/data-platform) --- -## Componentes Principais - -### 1. WebScraper (`webscraper.py`) - -Scraper genérico para sites gov.br. - -```python -class WebScraper: - """Raspa notícias de sites gov.br padrão.""" - - def __init__(self, base_url: str, agency: str): - self.base_url = base_url - self.agency = agency - - def scrape(self, start_date: date, end_date: date) -> list[dict]: - """Raspa notícias no intervalo de datas.""" - ... - - def fetch_article_content(self, url: str) -> str: - """Busca conteúdo completo e converte para Markdown.""" - ... -``` - -**Campos extraídos:** - -| Campo | Descrição | -|-------|-----------| -| `title` | Título da notícia | -| `subtitle` | Subtítulo (quando disponível) | -| `editorial_lead` | Lead editorial / linha fina | -| `url` | URL original | -| `published_at` | Data/hora de publicação (ISO 8601, UTC) | -| `updated_datetime` | Data/hora de atualização | -| `image` | URL da imagem principal | -| `video_url` | URL de vídeo incorporado | -| `content` | Conteúdo em Markdown | -| `category` | Categoria original do site | -| `tags` | Tags/keywords | - -**Retry Logic:** -```python -@retry(tries=5, delay=2, backoff=3, jitter=(1,3)) -def fetch_page(url: str) -> Response: - """Fetch com retry exponencial.""" - ... -``` - -### 2. EBCWebScraper (`ebc_webscraper.py`) - -Scraper especializado para sites da EBC (Agência Brasil, etc). - -```python -class EBCWebScraper(WebScraper): - """Scraper para sites EBC com estrutura HTML diferente.""" - - def parse_article(self, html: str) -> dict: - """Parser específico para estrutura EBC.""" - ... -``` - -### 3. DatasetManager (`dataset_manager.py`) +## Documentação Atualizada -Gerencia operações com o dataset HuggingFace. +A funcionalidade de scraping agora faz parte do repositório unificado `data-platform`. Consulte: -```python -class DatasetManager: - """Gerenciador de dataset HuggingFace.""" +- **[Data Platform](data-platform.md)** - Visão geral do repositório unificado +- **[Fluxo de Dados](../arquitetura/fluxo-de-dados.md)** - Pipeline completo de coleta e enriquecimento +- **[PostgreSQL](../arquitetura/postgresql.md)** - Banco de dados central (fonte de verdade) - def __init__(self, repo_id: str = "nitaibezerra/govbrnews"): - self.repo_id = repo_id +## Principais Mudanças - def load_dataset(self) -> pd.DataFrame: - """Carrega dataset completo.""" - ... +| Antes (scraper) | Agora (data-platform) | +|-----------------|----------------------| +| Repositório separado | Repositório unificado | +| HuggingFace como fonte de verdade | PostgreSQL como fonte de verdade | +| `python src/main.py scrape` | `data-platform scrape` | +| DatasetManager (HF) | PostgresManager + StorageAdapter | - def insert(self, articles: list[dict]) -> int: - """Insere novos artigos (deduplica por unique_id).""" - ... - - def update(self, df: pd.DataFrame, key: str = "unique_id") -> int: - """Atualiza registros existentes.""" - ... - - def push(self) -> None: - """Faz push para HuggingFace.""" - ... -``` - -**Geração de unique_id:** -```python -def generate_unique_id(agency: str, published_at: int, title: str) -> str: - """Gera ID único: MD5(agency + published_at + title)""" - content = f"{agency}{published_at}{title}" - return hashlib.md5(content.encode()).hexdigest() -``` - -### 4. CogfyManager (`cogfy_manager.py`) - -Cliente para API do Cogfy. - -```python -class CogfyManager: - """Cliente da API Cogfy para classificação e sumarização.""" - - def __init__(self, api_key: str, collection_id: str): - self.api_key = api_key - self.collection_id = collection_id - - def upload_records(self, records: list[dict]) -> list[str]: - """Envia registros para inferência LLM.""" - ... - - def get_record(self, unique_id: str) -> dict: - """Busca registro processado por unique_id.""" - ... -``` - -### 5. EnrichmentManager (`enrichment_manager.py`) - -Busca resultados do Cogfy e atualiza dataset. - -```python -class EnrichmentManager: - """Gerencia enriquecimento de dados via Cogfy.""" - - def enrich(self, start_date: date, end_date: date) -> int: - """Enriquece artigos no intervalo de datas.""" - # 1. Busca artigos no HuggingFace - # 2. Busca resultados no Cogfy - # 3. Mapeia códigos para labels - # 4. Calcula most_specific_theme - # 5. Atualiza dataset - ... -``` - -**Campos enriquecidos:** - -- `theme_1_level_1_code/label` - Tema nível 1 -- `theme_1_level_2_code/label` - Tema nível 2 -- `theme_1_level_3_code/label` - Tema nível 3 -- `most_specific_theme_code/label` - Tema mais específico disponível -- `summary` - Resumo gerado por LLM - ---- - -## Diagrama de Classes - -```mermaid -classDiagram - class WebScraper { - +base_url: str - +agency: str - +scrape(start_date, end_date) list - +fetch_article_content(url) str - -parse_listing_page(html) list - -parse_article_page(html) dict - } - - class EBCWebScraper { - +parse_article(html) dict - } - - class ScrapeManager { - +scrapers: list - +run_parallel() list - +run_sequential() list - } - - class DatasetManager { - +repo_id: str - +load_dataset() DataFrame - +insert(articles) int - +update(df) int - +push() void - } - - class CogfyManager { - +api_key: str - +collection_id: str - +upload_records(records) list - +get_record(unique_id) dict - } - - class EnrichmentManager { - +cogfy: CogfyManager - +dataset: DatasetManager - +enrich(start_date, end_date) int - } - - EBCWebScraper --|> WebScraper - ScrapeManager --> WebScraper - EnrichmentManager --> CogfyManager - EnrichmentManager --> DatasetManager -``` - ---- - -## CLI (main.py) - -O scraper usa Typer para CLI: - -```bash -# Ver ajuda -python src/main.py --help - -# Comandos disponíveis -python src/main.py scrape # Raspar sites gov.br -python src/main.py scrape-ebc # Raspar sites EBC -python src/main.py upload-cogfy # Upload para Cogfy -python src/main.py enrich # Buscar enriquecimento -``` - -### Exemplos de uso - -```bash -# Raspar últimos 7 dias -python src/main.py scrape \ - --start-date $(date -v-7d +%Y-%m-%d) \ - --end-date $(date +%Y-%m-%d) - -# Raspar período específico -python src/main.py scrape \ - --start-date 2024-12-01 \ - --end-date 2024-12-03 - -# Raspar EBC com atualização de existentes -python src/main.py scrape-ebc \ - --start-date 2024-12-01 \ - --end-date 2024-12-03 \ - --allow-update - -# Upload para Cogfy -python src/upload_to_cogfy_manager.py \ - --start-date 2024-12-01 \ - --end-date 2024-12-03 - -# Enriquecimento (após ~20 min do upload) -python src/enrichment_manager.py \ - --start-date 2024-12-01 \ - --end-date 2024-12-03 -``` - ---- - -## Configuração - -### Variáveis de Ambiente - -```bash -# .env -HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # Token HuggingFace (escrita) -COGFY_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxx # API Key do Cogfy -COGFY_COLLECTION_ID=xxxxxxxx-xxxx-xxxx # ID da collection Cogfy -``` - -### Arquivos de Configuração - -#### `site_urls.yaml` - URLs de raspagem - -```yaml -# Lista de URLs a raspar -urls: - - url: https://www.gov.br/gestao/pt-br/assuntos/noticias - agency: gestao - - url: https://www.gov.br/fazenda/pt-br/assuntos/noticias - agency: fazenda - # ... ~160+ URLs -``` - -#### `agencies.yaml` - Mapeamento de órgãos - -```yaml -# Mapeamento ID → Nome completo -agencies: - gestao: Ministério da Gestão e da Inovação em Serviços Públicos - fazenda: Ministério da Fazenda - saude: Ministério da Saúde - # ... 156 órgãos -``` - -#### `themes_tree.yaml` - Árvore temática - -```yaml -# Taxonomia de 25 temas × 3 níveis -01 - Economia e Finanças: - 01.01 - Política Econômica: - - 01.01.01 - Política Fiscal - - 01.01.02 - Autonomia Econômica - 01.02 - Fiscalização e Tributação: - - 01.02.01 - Fiscalização Econômica -# ... -``` - ---- - -## Como Adicionar Nova Fonte - -### 1. Adicionar URL - -Editar `src/scraper/site_urls.yaml`: - -```yaml -urls: - # Adicionar nova entrada - - url: https://www.gov.br/novo-orgao/pt-br/assuntos/noticias - agency: novo-orgao -``` - -### 2. Adicionar Mapeamento - -Editar `src/scraper/agencies.yaml`: - -```yaml -agencies: - novo-orgao: Nome Completo do Novo Órgão -``` - -### 3. Testar Localmente +## CLI Atual ```bash -# Testar raspagem do novo órgão -python src/main.py scrape \ - --start-date $(date +%Y-%m-%d) \ - --end-date $(date +%Y-%m-%d) -``` +# Raspagem de sites gov.br +data-platform scrape --start-date YYYY-MM-DD --end-date YYYY-MM-DD -### 4. Verificar Compatibilidade +# Raspagem de sites EBC +data-platform scrape-ebc --start-date YYYY-MM-DD --end-date YYYY-MM-DD -Se o site tiver estrutura HTML diferente: +# Upload para Cogfy (enriquecimento) +data-platform upload-cogfy --start-date YYYY-MM-DD --end-date YYYY-MM-DD -1. Analisar estrutura no navegador -2. Criar scraper especializado se necessário -3. Adicionar testes - ---- - -## Testes - -```bash -# Executar todos os testes -poetry run pytest - -# Com coverage -poetry run pytest --cov=src - -# Teste específico -poetry run pytest tests/test_webscraper.py -v - -# Testes de integração (requer credenciais) -poetry run pytest tests/integration/ -v +# Buscar enriquecimento do Cogfy +data-platform enrich --start-date YYYY-MM-DD --end-date YYYY-MM-DD ``` ---- - -## Docker - -```bash -# Build -docker build -t scraper . - -# Executar -docker run --env-file .env scraper \ - python src/main.py scrape --start-date 2024-12-01 --end-date 2024-12-01 -``` - ---- - -## Links Relacionados +## Arquivos Principais (Novo Local) -- [Fluxo de Dados](../arquitetura/fluxo-de-dados.md) - Pipeline completo -- [Componentes Estruturantes](../arquitetura/componentes-estruturantes.md) - Árvore temática e órgãos -- [Integração Cogfy](./cogfy-integracao.md) - Detalhes da classificação LLM -- [Pipeline Scraper](../workflows/scraper-pipeline.md) - GitHub Actions +| Componente | Localização | +|------------|-------------| +| WebScraper | `src/data_platform/scrapers/webscraper.py` | +| EBCWebScraper | `src/data_platform/scrapers/ebc_webscraper.py` | +| ScrapeManager | `src/data_platform/scrapers/scrape_manager.py` | +| PostgresManager | `src/data_platform/managers/postgres_manager.py` | +| StorageAdapter | `src/data_platform/managers/storage_adapter.py` | +| site_urls.yaml | `src/data_platform/scrapers/site_urls.yaml` | +| agencies.yaml | `src/data_platform/scrapers/agencies.yaml` | diff --git a/docs/modulos/typesense-local.md b/docs/modulos/typesense-local.md index 6b0b2f1..9eca8c5 100644 --- a/docs/modulos/typesense-local.md +++ b/docs/modulos/typesense-local.md @@ -1,84 +1,33 @@ -# Módulo: Typesense Local (typesense) +# Módulo: Typesense Local -> Ambiente de desenvolvimento local para busca full-text. +> Ambiente de desenvolvimento local para busca full-text e vetorial. -**Repositório**: [github.com/destaquesgovbr/typesense](https://github.com/destaquesgovbr/typesense) +!!! info "Módulo Migrado" + Os scripts de sincronização com Typesense foram migrados para o repositório **data-platform**. + O repositório `typesense` foi arquivado. -## Visão Geral + **Novo repositório**: [github.com/destaquesgovbr/data-platform](https://github.com/destaquesgovbr/data-platform) -O repositório `typesense` fornece: +## Visão Geral -- **Docker Compose** para rodar Typesense localmente -- **Scripts Python** para carregar dados do HuggingFace -- **Configuração** da collection `news` +O Typesense é usado para busca full-text e vetorial no portal. Em desenvolvimento local, você pode usar Docker Compose para rodar uma instância local. ```mermaid flowchart LR - HF[(HuggingFace)] -->|Download| SC[Scripts Python] + PG[(PostgreSQL)] -->|Sync| SC[data-platform CLI] SC -->|Upsert| TS[(Typesense Local)] TS -->|Busca| PO[Portal Dev] ``` --- -## Estrutura do Repositório - -``` -typesense/ -├── docker-compose.yml # Typesense container -├── python/ -│ ├── scripts/ -│ │ └── load_data.py # Script de carga -│ ├── requirements.txt # Dependências Python -│ └── config.py # Configurações -└── README.md -``` - ---- - ## Quick Start -### 1. Clonar repositório +### 1. Subir Typesense com Docker ```bash -git clone https://github.com/destaquesgovbr/typesense.git -cd typesense -``` - -### 2. Subir Typesense - -```bash -docker compose up -d -``` - -### 3. Verificar se está rodando - -```bash -curl http://localhost:8108/health -# Resposta: {"ok":true} -``` - -### 4. Carregar dados - -```bash -cd python -pip install -r requirements.txt -python scripts/load_data.py --mode incremental --days 7 -``` - -### 5. Testar busca - -```bash -curl "http://localhost:8108/collections/news/documents/search?q=economia&query_by=title" \ - -H "X-TYPESENSE-API-KEY: xyz" -``` - ---- - -## Docker Compose - -```yaml -# docker-compose.yml +# Criar docker-compose.yml +cat > docker-compose.yml << 'EOF' version: '3.8' services: @@ -96,65 +45,59 @@ services: volumes: typesense-data: +EOF + +docker compose up -d ``` -### Comandos Docker +### 2. Verificar se está rodando ```bash -# Subir -docker compose up -d +curl http://localhost:8108/health +# Resposta: {"ok":true} +``` -# Ver logs -docker compose logs -f typesense +### 3. Carregar dados (via data-platform) -# Parar -docker compose down +```bash +# No diretório data-platform +data-platform sync-typesense --start-date $(date -v-7d +%Y-%m-%d) +``` -# Remover dados (reset completo) -docker compose down -v +### 4. Testar busca + +```bash +curl "http://localhost:8108/collections/news/documents/search?q=economia&query_by=title" \ + -H "X-TYPESENSE-API-KEY: xyz" ``` --- -## Script de Carga (`load_data.py`) - -### Modos de operação +## Sincronização com PostgreSQL -| Modo | Descrição | Uso | -|------|-----------|-----| -| `incremental` | Carrega últimos N dias | Desenvolvimento diário | -| `full` | Carrega dataset completo | Reset ou primeira carga | +A sincronização agora lê do **PostgreSQL** (fonte de verdade) e escreve no Typesense. -### Exemplos +### CLI do data-platform ```bash -# Carregar últimos 7 dias -python scripts/load_data.py --mode incremental --days 7 +# Sync incremental (últimos N dias) +data-platform sync-typesense --start-date YYYY-MM-DD -# Carregar últimos 30 dias -python scripts/load_data.py --mode incremental --days 30 - -# Carga completa (demora mais) -python scripts/load_data.py --mode full +# Sync completo (todos os registros) +data-platform sync-typesense --full-sync ``` -### Funcionamento interno +### Fluxo Interno ```python -def load_incremental(days: int): - """Carrega dados dos últimos N dias.""" - # 1. Conectar ao Typesense - # 2. Baixar dataset do HuggingFace - # 3. Filtrar por data - # 4. Upsert documentos - pass - -def load_full(): - """Carga completa do dataset.""" - # 1. Deletar collection existente - # 2. Criar collection com schema - # 3. Baixar dataset completo - # 4. Inserir todos os documentos +# src/data_platform/jobs/typesense/sync_job.py +def sync_typesense(start_date: date = None, full_sync: bool = False): + """Sincroniza PostgreSQL → Typesense.""" + # 1. Conectar ao PostgreSQL + # 2. Conectar ao Typesense + # 3. Buscar notícias (com embeddings) + # 4. Converter para schema Typesense + # 5. Upsert em batches de 5000 pass ``` @@ -162,32 +105,48 @@ def load_full(): ## Schema da Collection +O schema inclui campos para busca vetorial: + ```python schema = { "name": "news", "fields": [ + # Identificação {"name": "unique_id", "type": "string"}, {"name": "agency", "type": "string", "facet": True}, + {"name": "agency_name", "type": "string", "facet": True}, + + # Conteúdo {"name": "title", "type": "string"}, {"name": "url", "type": "string"}, {"name": "image", "type": "string", "optional": True}, {"name": "content", "type": "string"}, + {"name": "summary", "type": "string", "optional": True}, + + # Datas {"name": "published_at", "type": "int64", "sort": True}, + + # Classificação original {"name": "category", "type": "string", "optional": True, "facet": True}, {"name": "tags", "type": "string[]", "optional": True}, - # Campos de tema - {"name": "theme_1_level_1_code", "type": "string", "optional": True, "facet": True}, - {"name": "theme_1_level_1_label", "type": "string", "optional": True, "facet": True}, - {"name": "theme_1_level_2_code", "type": "string", "optional": True, "facet": True}, - {"name": "theme_1_level_2_label", "type": "string", "optional": True}, - {"name": "theme_1_level_3_code", "type": "string", "optional": True, "facet": True}, - {"name": "theme_1_level_3_label", "type": "string", "optional": True}, + # Temas (enriquecimento Cogfy) + {"name": "theme_l1_code", "type": "string", "optional": True, "facet": True}, + {"name": "theme_l1_label", "type": "string", "optional": True, "facet": True}, + {"name": "theme_l2_code", "type": "string", "optional": True, "facet": True}, + {"name": "theme_l2_label", "type": "string", "optional": True}, + {"name": "theme_l3_code", "type": "string", "optional": True, "facet": True}, + {"name": "theme_l3_label", "type": "string", "optional": True}, {"name": "most_specific_theme_code", "type": "string", "optional": True, "facet": True}, {"name": "most_specific_theme_label", "type": "string", "optional": True}, - # Resumo AI - {"name": "summary", "type": "string", "optional": True}, + # Embedding para busca vetorial + { + "name": "content_embedding", + "type": "float[]", + "num_dim": 768, + "optional": True + }, ], "default_sorting_field": "published_at" } @@ -195,6 +154,25 @@ schema = { --- +## Busca Vetorial + +O Typesense em produção suporta busca semântica via embeddings: + +```bash +# Busca vetorial (requer embedding da query) +curl -X POST "http://localhost:8108/multi_search" \ + -H "X-TYPESENSE-API-KEY: xyz" \ + -d '{ + "searches": [{ + "collection": "news", + "q": "*", + "vector_query": "content_embedding:([0.1, 0.2, ...], k:10)" + }] + }' +``` + +--- + ## API do Typesense ### Verificar saúde @@ -236,14 +214,14 @@ per_page=10" \ -H "X-TYPESENSE-API-KEY: xyz" ``` -### Busca por múltiplos valores +### Busca por tema ```bash curl "http://localhost:8108/collections/news/documents/search?\ q=*&\ query_by=title&\ -filter_by=agency:[gestao,fazenda,saude]&\ -filter_by=theme_1_level_1_code:[01,03]" \ +filter_by=theme_l1_code:01&\ +sort_by=published_at:desc" \ -H "X-TYPESENSE-API-KEY: xyz" ``` @@ -317,14 +295,7 @@ curl http://localhost:8108/collections/news \ 2. Recarregar dados: ```bash -python scripts/load_data.py --mode incremental --days 7 -``` - -### Dados desatualizados - -```bash -# Recarregar últimos dias -python scripts/load_data.py --mode incremental --days 30 +data-platform sync-typesense --start-date $(date -v-7d +%Y-%m-%d) ``` ### Reset completo @@ -337,7 +308,7 @@ docker compose down -v docker compose up -d # Recarregar dados -python scripts/load_data.py --mode full +data-platform sync-typesense --full-sync ``` --- @@ -350,7 +321,9 @@ python scripts/load_data.py --mode full | Porta | 8108 | 8108 | | API Key | xyz | Secret Manager | | Dados | Últimos N dias | Dataset completo | +| Embeddings | Opcional | Incluídos | | Persistência | Volume Docker | Disco persistente | +| Fonte de dados | PostgreSQL | PostgreSQL | --- @@ -366,13 +339,24 @@ python scripts/load_data.py --mode full ```bash # Carregar menos dados para desenvolvimento -python scripts/load_data.py --mode incremental --days 3 +data-platform sync-typesense --start-date $(date -v-3d +%Y-%m-%d) ``` --- +## Arquivos Principais (data-platform) + +| Componente | Localização | +|------------|-------------| +| TypesenseClient | `src/data_platform/typesense/client.py` | +| TypesenseIndexer | `src/data_platform/typesense/indexer.py` | +| SyncJob | `src/data_platform/jobs/typesense/sync_job.py` | + +--- + ## Links Relacionados +- [Data Platform](data-platform.md) - Repositório unificado +- [PostgreSQL](../arquitetura/postgresql.md) - Fonte de verdade - [Setup Frontend](../onboarding/setup-frontend.md) - Configuração do portal -- [Workflow Typesense Data](../workflows/typesense-data.md) - Carga em produção -- [Módulo Portal](./portal.md) - Uso do Typesense no portal +- [Workflow Typesense Data](../workflows/typesense-data.md) - Sync em produção diff --git a/docs/workflows/scraper-pipeline.md b/docs/workflows/scraper-pipeline.md index 29da0af..2deaea7 100644 --- a/docs/workflows/scraper-pipeline.md +++ b/docs/workflows/scraper-pipeline.md @@ -2,11 +2,11 @@ > Pipeline diário de coleta e enriquecimento de notícias. -**Arquivo**: `scraper/.github/workflows/main-workflow.yaml` +**Arquivo**: `data-platform/.github/workflows/main-workflow.yaml` ## Visão Geral -O pipeline é executado diariamente às **4AM UTC** (1AM Brasília) e consiste em 5 jobs sequenciais: +O pipeline é executado diariamente às **4AM UTC** (1AM Brasília) e consiste em 7 jobs sequenciais: ```mermaid flowchart LR @@ -14,6 +14,8 @@ flowchart LR B --> C[upload-to-cogfy] C --> D[wait-cogfy] D --> E[enrich-themes] + E --> F[generate-embeddings] + F --> G[sync-typesense] ``` --- @@ -26,10 +28,10 @@ on: - cron: '0 4 * * *' # 4AM UTC diário workflow_dispatch: # Manual inputs: - min-date: + start-date: description: 'Data inicial (YYYY-MM-DD)' required: false - max-date: + end-date: description: 'Data final (YYYY-MM-DD)' required: false ``` @@ -50,8 +52,8 @@ gh workflow run main-workflow.yaml # Período específico gh workflow run main-workflow.yaml \ - -f min-date=2024-12-01 \ - -f max-date=2024-12-03 + -f start-date=2024-12-01 \ + -f end-date=2024-12-03 ``` --- @@ -60,21 +62,24 @@ gh workflow run main-workflow.yaml \ ### Job 1: `scraper` -Raspa notícias dos sites gov.br. +Raspa notícias dos sites gov.br e insere no PostgreSQL. ```yaml scraper: runs-on: ubuntu-latest container: - image: ghcr.io/destaquesgovbr/scraper:latest + image: ghcr.io/destaquesgovbr/data-platform:latest steps: - name: Scrape gov.br sites run: | - python src/main.py scrape \ - --start-date ${{ inputs.min-date || steps.dates.outputs.min }} \ - --end-date ${{ inputs.max-date || steps.dates.outputs.max }} + data-platform scrape \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} \ + --end-date ${{ inputs.end-date || steps.dates.outputs.end }} env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} ``` **Duração**: ~30-60 minutos (dependendo do período) @@ -88,38 +93,44 @@ ebc-scraper: needs: scraper runs-on: ubuntu-latest container: - image: ghcr.io/destaquesgovbr/scraper:latest + image: ghcr.io/destaquesgovbr/data-platform:latest steps: - name: Scrape EBC sites run: | - python src/main.py scrape-ebc \ - --start-date ${{ inputs.min-date || steps.dates.outputs.min }} \ - --end-date ${{ inputs.max-date || steps.dates.outputs.max }} \ + data-platform scrape-ebc \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} \ + --end-date ${{ inputs.end-date || steps.dates.outputs.end }} \ --allow-update env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} ``` **Duração**: ~10-20 minutos ### Job 3: `upload-to-cogfy` -Envia notícias para classificação no Cogfy. +Envia notícias do PostgreSQL para classificação no Cogfy. ```yaml upload-to-cogfy: needs: ebc-scraper runs-on: ubuntu-latest container: - image: ghcr.io/destaquesgovbr/scraper:latest + image: ghcr.io/destaquesgovbr/data-platform:latest steps: - name: Upload to Cogfy run: | - python src/upload_to_cogfy_manager.py \ - --start-date ${{ inputs.min-date || steps.dates.outputs.min }} \ - --end-date ${{ inputs.max-date || steps.dates.outputs.max }} + data-platform upload-cogfy \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} \ + --end-date ${{ inputs.end-date || steps.dates.outputs.end }} env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} COGFY_API_KEY: ${{ secrets.COGFY_API_KEY }} COGFY_COLLECTION_ID: ${{ secrets.COGFY_COLLECTION_ID }} ``` @@ -143,28 +154,82 @@ wait-cogfy: ### Job 5: `enrich-themes` -Busca resultados do Cogfy e atualiza dataset. +Busca resultados do Cogfy e atualiza PostgreSQL. ```yaml enrich-themes: needs: wait-cogfy runs-on: ubuntu-latest container: - image: ghcr.io/destaquesgovbr/scraper:latest + image: ghcr.io/destaquesgovbr/data-platform:latest steps: - name: Enrich with themes run: | - python src/enrichment_manager.py \ - --start-date ${{ inputs.min-date || steps.dates.outputs.min }} \ - --end-date ${{ inputs.max-date || steps.dates.outputs.max }} + data-platform enrich \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} \ + --end-date ${{ inputs.end-date || steps.dates.outputs.end }} env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} COGFY_API_KEY: ${{ secrets.COGFY_API_KEY }} COGFY_COLLECTION_ID: ${{ secrets.COGFY_COLLECTION_ID }} ``` **Duração**: ~10-20 minutos +### Job 6: `generate-embeddings` + +Gera embeddings para notícias sem vetores. + +```yaml +generate-embeddings: + needs: enrich-themes + runs-on: ubuntu-latest + container: + image: ghcr.io/destaquesgovbr/data-platform:latest + steps: + - name: Generate embeddings + run: | + data-platform generate-embeddings \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} + env: + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + EMBEDDINGS_API_URL: ${{ secrets.EMBEDDINGS_API_URL }} +``` + +**Duração**: ~10-15 minutos + +### Job 7: `sync-typesense` + +Sincroniza dados do PostgreSQL para o Typesense. + +```yaml +sync-typesense: + needs: generate-embeddings + runs-on: ubuntu-latest + container: + image: ghcr.io/destaquesgovbr/data-platform:latest + steps: + - name: Sync to Typesense + run: | + data-platform sync-typesense \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} + env: + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + TYPESENSE_HOST: ${{ secrets.TYPESENSE_HOST }} + TYPESENSE_API_KEY: ${{ secrets.TYPESENSE_API_KEY }} +``` + +**Duração**: ~5-10 minutos + --- ## Diagrama de Sequência @@ -172,35 +237,47 @@ enrich-themes: ```mermaid sequenceDiagram participant GH as GitHub Actions - participant SC as Scraper Container + participant DP as Data Platform Container participant GOV as Sites gov.br participant EBC as Sites EBC - participant HF as HuggingFace + participant PG as PostgreSQL participant CF as Cogfy + participant EMB as Embeddings API + participant TS as Typesense Note over GH: 4AM UTC - Trigger - GH->>SC: Job: scraper - SC->>GOV: Fetch ~160+ sites - GOV-->>SC: HTML pages - SC->>SC: Parse → Markdown - SC->>HF: Insert articles + GH->>DP: Job: scraper + DP->>GOV: Fetch ~160+ sites + GOV-->>DP: HTML pages + DP->>DP: Parse → Markdown + DP->>PG: Insert articles - GH->>SC: Job: ebc-scraper - SC->>EBC: Fetch EBC sites - EBC-->>SC: HTML pages - SC->>HF: Insert/Update articles + GH->>DP: Job: ebc-scraper + DP->>EBC: Fetch EBC sites + EBC-->>DP: HTML pages + DP->>PG: Insert/Update articles - GH->>SC: Job: upload-to-cogfy - SC->>HF: Load articles - SC->>CF: POST records (batch) + GH->>DP: Job: upload-to-cogfy + DP->>PG: Load articles + DP->>CF: POST records (batch) Note over GH: Wait 20 min - GH->>SC: Job: enrich-themes - SC->>CF: GET processed records - CF-->>SC: Themes + Summary - SC->>HF: Update with enrichment + GH->>DP: Job: enrich-themes + DP->>CF: GET processed records + CF-->>DP: Themes + Summary + DP->>PG: Update with enrichment + + GH->>DP: Job: generate-embeddings + DP->>PG: Get news without embeddings + DP->>EMB: POST texts (batch) + EMB-->>DP: Vectors 768-dim + DP->>PG: Update with embeddings + + GH->>DP: Job: sync-typesense + DP->>PG: Get news for indexing + DP->>TS: Upsert documents ``` --- @@ -209,17 +286,29 @@ sequenceDiagram | Secret | Descrição | Usado em | |--------|-----------|----------| -| `HF_TOKEN` | Token HuggingFace (write) | Todos os jobs | +| `POSTGRES_HOST` | Host do Cloud SQL | Todos os jobs | +| `POSTGRES_DB` | Nome do banco | Todos os jobs | +| `POSTGRES_USER` | Usuário do banco | Todos os jobs | +| `POSTGRES_PASSWORD` | Senha do banco | Todos os jobs | | `COGFY_API_KEY` | API Key do Cogfy | upload, enrich | | `COGFY_COLLECTION_ID` | ID da collection Cogfy | upload, enrich | +| `EMBEDDINGS_API_URL` | URL da API de embeddings | embeddings | +| `TYPESENSE_HOST` | Host do Typesense | sync-typesense | +| `TYPESENSE_API_KEY` | API Key do Typesense | sync-typesense | ### Configurar secrets ```bash # Via GitHub CLI -gh secret set HF_TOKEN --body "hf_xxxxx" +gh secret set POSTGRES_HOST --body "10.x.x.x" +gh secret set POSTGRES_DB --body "destaquesgovbr" +gh secret set POSTGRES_USER --body "admin" +gh secret set POSTGRES_PASSWORD --body "xxxxx" gh secret set COGFY_API_KEY --body "sk-xxxxx" gh secret set COGFY_COLLECTION_ID --body "uuid-xxxxx" +gh secret set EMBEDDINGS_API_URL --body "https://embeddings-xxx.run.app" +gh secret set TYPESENSE_HOST --body "10.x.x.x" +gh secret set TYPESENSE_API_KEY --body "xxxxx" ``` --- @@ -262,10 +351,15 @@ gh run view --log - Enriquecimento não executa - Dados ficam sem classificação até próxima execução -### Falha em enriquecimento +### Falha em embeddings + +- Notícias sem embeddings são marcadas +- Próxima execução tenta novamente -- Dataset mantém dados sem enriquecimento -- Próxima execução pode recuperar +### Falha em sync Typesense + +- Portal continua funcionando com dados antigos +- Próxima execução tenta novamente --- @@ -275,8 +369,8 @@ gh run view --log ```bash gh workflow run main-workflow.yaml \ - -f min-date=2024-01-01 \ - -f max-date=2024-01-31 + -f start-date=2024-01-01 \ + -f end-date=2024-01-31 ``` ### Para reprocessar @@ -284,42 +378,8 @@ gh workflow run main-workflow.yaml \ ```bash # Reprocessar últimos 7 dias gh workflow run main-workflow.yaml \ - -f min-date=$(date -v-7d +%Y-%m-%d) \ - -f max-date=$(date +%Y-%m-%d) -``` - ---- - -## Workflow Separado: Dispatch - -Existe também `scraper-dispatch.yaml` para execuções manuais isoladas: - -```yaml -# scraper-dispatch.yaml -on: - workflow_dispatch: - inputs: - min-date: - required: true - max-date: - required: true - job: - type: choice - options: - - scrape - - ebc-scrape - - upload-cogfy - - enrich -``` - -Uso: - -```bash -# Apenas scraping -gh workflow run scraper-dispatch.yaml \ - -f min-date=2024-12-01 \ - -f max-date=2024-12-03 \ - -f job=scrape + -f start-date=$(date -v-7d +%Y-%m-%d) \ + -f end-date=$(date +%Y-%m-%d) ``` --- @@ -333,13 +393,24 @@ gh workflow run scraper-dispatch.yaml \ | upload-to-cogfy | 5-10 min | | wait-cogfy | 20 min (fixo) | | enrich-themes | 10-20 min | -| **Total** | **~75-130 min** | +| generate-embeddings | 10-15 min | +| sync-typesense | 5-10 min | +| **Total** | **~90-155 min** | + +--- + +## Sync HuggingFace (Separado) + +O sync para o HuggingFace é feito via DAG no Cloud Composer, não faz parte deste workflow. + +→ Veja [Airflow DAGs](./airflow-dags.md) para detalhes. --- ## Links Relacionados +- [Data Platform](../modulos/data-platform.md) - Repositório unificado +- [PostgreSQL](../arquitetura/postgresql.md) - Fonte de verdade - [Fluxo de Dados](../arquitetura/fluxo-de-dados.md) - Visão geral do pipeline -- [Módulo Scraper](../modulos/scraper.md) - Detalhes do scraper - [Integração Cogfy](../modulos/cogfy-integracao.md) - Classificação LLM - [Docker Builds](./docker-builds.md) - Build da imagem diff --git a/docs/workflows/typesense-data.md b/docs/workflows/typesense-data.md index a342b5c..ececb1f 100644 --- a/docs/workflows/typesense-data.md +++ b/docs/workflows/typesense-data.md @@ -4,8 +4,8 @@ **Arquivos**: -- `infra/.github/workflows/typesense-daily-load.yml` -- `infra/.github/workflows/typesense-full-reload.yml` +- `data-platform/.github/workflows/typesense-maintenance-sync.yaml` +- `data-platform/.github/workflows/typesense-full-reload.yaml` ## Visão Geral @@ -13,23 +13,23 @@ Existem dois workflows para gerenciar dados no Typesense: | Workflow | Uso | Frequência | |----------|-----|------------| -| `typesense-daily-load` | Carga incremental | Diário (10AM UTC) | +| `typesense-maintenance-sync` | Sync incremental | Diário (10AM UTC) | | `typesense-full-reload` | Recarga completa | Manual (destrutivo) | ```mermaid flowchart TB - subgraph "Daily Load" - A[HuggingFace] -->|Últimos 7 dias| B[Upsert Typesense] + subgraph "Maintenance Sync" + A[(PostgreSQL)] -->|Últimos N dias| B[Upsert Typesense] end subgraph "Full Reload" - C[HuggingFace] -->|Dataset completo| D[Delete + Insert] + C[(PostgreSQL)] -->|Dataset completo| D[Delete + Insert] end ``` --- -## Workflow: Daily Load +## Workflow: Maintenance Sync ### Trigger @@ -39,9 +39,9 @@ on: - cron: '0 10 * * *' # 10AM UTC diário workflow_dispatch: inputs: - days: - description: 'Número de dias para carregar' - default: '7' + start-date: + description: 'Data inicial (YYYY-MM-DD)' + required: false ``` ### Execução @@ -49,12 +49,12 @@ on: ```mermaid sequenceDiagram participant GH as GitHub Actions - participant HF as HuggingFace + participant PG as PostgreSQL participant TS as Typesense Note over GH: 10AM UTC - GH->>HF: Download últimos N dias - HF-->>GH: Parquet data + GH->>PG: Query últimos N dias + PG-->>GH: News with embeddings GH->>TS: Upsert documents TS-->>GH: Success ``` @@ -62,39 +62,32 @@ sequenceDiagram ### Job ```yaml -daily-load: +sync: runs-on: ubuntu-latest + container: + image: ghcr.io/destaquesgovbr/data-platform:latest steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: pip install -r python/requirements.txt - - - name: Load data + - name: Sync to Typesense run: | - python python/scripts/load_data.py \ - --mode incremental \ - --days ${{ inputs.days || 7 }} + data-platform sync-typesense \ + --start-date ${{ inputs.start-date || steps.dates.outputs.start }} env: + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} TYPESENSE_HOST: ${{ secrets.TYPESENSE_HOST }} - TYPESENSE_PORT: ${{ secrets.TYPESENSE_PORT }} TYPESENSE_API_KEY: ${{ secrets.TYPESENSE_API_KEY }} ``` ### Execução manual ```bash -# Com padrão de 7 dias -gh workflow run typesense-daily-load.yml +# Com padrão (últimos 7 dias) +gh workflow run typesense-maintenance-sync.yaml -# Com número específico de dias -gh workflow run typesense-daily-load.yml -f days=30 +# Com data específica +gh workflow run typesense-maintenance-sync.yaml -f start-date=2024-12-01 ``` --- @@ -132,92 +125,112 @@ full-reload: ```yaml full-reload: runs-on: ubuntu-latest + container: + image: ghcr.io/destaquesgovbr/data-platform:latest steps: - name: Validate confirmation if: inputs.confirm != 'DELETE' run: exit 1 - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - - - name: Install dependencies - run: pip install -r python/requirements.txt - - name: Full reload run: | - python python/scripts/load_data.py --mode full + data-platform sync-typesense --full-sync env: + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} TYPESENSE_HOST: ${{ secrets.TYPESENSE_HOST }} - TYPESENSE_PORT: ${{ secrets.TYPESENSE_PORT }} TYPESENSE_API_KEY: ${{ secrets.TYPESENSE_API_KEY }} ``` ### Execução ```bash -gh workflow run typesense-full-reload.yml -f confirm=DELETE +gh workflow run typesense-full-reload.yaml -f confirm=DELETE ``` --- -## Script de Carga (`load_data.py`) +## CLI do data-platform + +### Sync Incremental + +```bash +# Sync desde uma data +data-platform sync-typesense --start-date 2024-12-01 + +# Sync últimos 7 dias (padrão do workflow) +data-platform sync-typesense --start-date $(date -v-7d +%Y-%m-%d) +``` + +### Sync Completo + +```bash +# Full sync (destrutivo) +data-platform sync-typesense --full-sync +``` -### Modo Incremental +### Funcionamento Interno ```python -def load_incremental(days: int): - """Carrega últimos N dias via upsert.""" - # 1. Conectar ao Typesense - client = get_typesense_client() - - # 2. Baixar dataset do HuggingFace - ds = load_dataset("nitaibezerra/govbrnews") - df = ds["train"].to_pandas() - - # 3. Filtrar por data - cutoff = datetime.now() - timedelta(days=days) - df = df[df["published_at"] >= cutoff.timestamp()] - - # 4. Upsert documentos - for batch in chunks(df.to_dict("records"), 1000): - client.collections["news"].documents.import_( - batch, - {"action": "upsert"} - ) - - print(f"✅ Loaded {len(df)} documents") +# src/data_platform/jobs/typesense/sync_job.py +def sync_typesense(start_date: date = None, full_sync: bool = False): + """Sincroniza PostgreSQL → Typesense.""" + # 1. Conectar ao PostgreSQL + pg = PostgresManager() + + # 2. Conectar ao Typesense + ts = TypesenseClient() + + if full_sync: + # 3a. Full: deletar e recriar collection + ts.delete_collection("news") + ts.create_collection("news", NEWS_SCHEMA) + + # 4a. Buscar todos os registros + news = pg.iter_news_for_typesense() + else: + # 3b. Incremental: apenas upsert + # 4b. Buscar registros desde start_date + news = pg.iter_news_for_typesense(since=start_date) + + # 5. Upsert em batches de 5000 + for batch in chunks(news, 5000): + ts.upsert_documents("news", batch) ``` -### Modo Full +--- + +## Dados Sincronizados + +O sync inclui embeddings para busca vetorial: ```python -def load_full(): - """Recarga completa (destrutivo).""" - client = get_typesense_client() - - # 1. Deletar collection existente - try: - client.collections["news"].delete() - print("🗑️ Collection deleted") - except Exception: - pass - - # 2. Criar collection com schema - client.collections.create(NEWS_SCHEMA) - print("📦 Collection created") - - # 3. Baixar dataset completo - ds = load_dataset("nitaibezerra/govbrnews") - df = ds["train"].to_pandas() - - # 4. Inserir todos os documentos - for batch in chunks(df.to_dict("records"), 1000): - client.collections["news"].documents.import_(batch) - - print(f"✅ Loaded {len(df)} documents") +document = { + "unique_id": row["unique_id"], + "agency": row["agency_key"], + "agency_name": row["agency_name"], + "title": row["title"], + "url": row["url"], + "image": row["image_url"], + "content": row["content"], + "summary": row["summary"], + "published_at": int(row["published_at"].timestamp()), + + # Temas + "theme_l1_code": theme_l1.code if theme_l1 else None, + "theme_l1_label": theme_l1.label if theme_l1 else None, + "theme_l2_code": theme_l2.code if theme_l2 else None, + "theme_l2_label": theme_l2.label if theme_l2 else None, + "theme_l3_code": theme_l3.code if theme_l3 else None, + "theme_l3_label": theme_l3.label if theme_l3 else None, + "most_specific_theme_code": most_specific.code if most_specific else None, + "most_specific_theme_label": most_specific.label if most_specific else None, + + # Embedding para busca vetorial + "content_embedding": row["content_embedding"], # 768 dims +} ``` --- @@ -226,19 +239,22 @@ def load_full(): | Secret | Descrição | |--------|-----------| +| `POSTGRES_HOST` | Host do Cloud SQL | +| `POSTGRES_DB` | Nome do banco | +| `POSTGRES_USER` | Usuário do banco | +| `POSTGRES_PASSWORD` | Senha do banco | | `TYPESENSE_HOST` | IP/hostname do Typesense | -| `TYPESENSE_PORT` | Porta (8108) | | `TYPESENSE_API_KEY` | API Key de admin | --- ## Quando Usar Cada Workflow -### Daily Load (Incremental) +### Maintenance Sync (Incremental) - ✅ Atualização diária normal - ✅ Correção de dados recentes -- ✅ Teste com período maior +- ✅ Após pipeline diário do scraper ### Full Reload @@ -246,6 +262,7 @@ def load_full(): - ⚠️ Dados corrompidos - ⚠️ Limpeza completa necessária - ⚠️ Primeira carga em novo ambiente +- ⚠️ Após adição de embeddings em massa --- @@ -254,22 +271,22 @@ def load_full(): ### Ver execuções ```bash -# Daily load -gh run list --workflow=typesense-daily-load.yml +# Maintenance sync +gh run list --workflow=typesense-maintenance-sync.yaml # Full reload -gh run list --workflow=typesense-full-reload.yml +gh run list --workflow=typesense-full-reload.yaml ``` ### Verificar dados no Typesense ```bash -# Via SSH no servidor -curl "http://localhost:8108/collections/news" \ +# Via SSH no servidor ou API +curl "http://:8108/collections/news" \ -H "X-TYPESENSE-API-KEY: $API_KEY" # Contagem de documentos -curl "http://localhost:8108/collections/news/documents/search?q=*&per_page=0" \ +curl "http://:8108/collections/news/documents/search?q=*&per_page=0" \ -H "X-TYPESENSE-API-KEY: $API_KEY" ``` @@ -277,23 +294,30 @@ curl "http://localhost:8108/collections/news/documents/search?q=*&per_page=0" \ ## Troubleshooting -### Daily load não atualiza +### Maintenance sync não atualiza -1. Verificar se HuggingFace tem dados novos +1. Verificar se PostgreSQL tem dados novos 2. Verificar logs do workflow -3. Executar manualmente com mais dias +3. Executar manualmente com data específica ### Full reload falha -1. Verificar conexão com Typesense -2. Verificar espaço em disco -3. Verificar memória disponível +1. Verificar conexão com PostgreSQL +2. Verificar conexão com Typesense +3. Verificar espaço em disco no servidor Typesense +4. Verificar memória disponível ### Dados inconsistentes 1. Executar full reload 2. Verificar schema da collection -3. Verificar dados no HuggingFace +3. Verificar dados no PostgreSQL + +### Embeddings ausentes + +1. Verificar se job `generate-embeddings` executou com sucesso +2. Verificar se registros têm `content_embedding` no PostgreSQL +3. Re-executar pipeline de embeddings se necessário --- @@ -301,14 +325,16 @@ curl "http://localhost:8108/collections/news/documents/search?q=*&per_page=0" \ | Workflow | Duração Típica | |----------|----------------| -| Daily Load (7 dias) | 5-10 min | -| Daily Load (30 dias) | 15-20 min | -| Full Reload | 30-60 min | +| Maintenance Sync (7 dias) | 5-10 min | +| Maintenance Sync (30 dias) | 15-20 min | +| Full Reload | 45-90 min | --- ## Links Relacionados +- [Data Platform](../modulos/data-platform.md) - Repositório unificado +- [PostgreSQL](../arquitetura/postgresql.md) - Fonte de verdade - [Typesense Local](../modulos/typesense-local.md) - Ambiente de desenvolvimento - [Fluxo de Dados](../arquitetura/fluxo-de-dados.md) - Pipeline completo - [Arquitetura GCP](../infraestrutura/arquitetura-gcp.md) - Infraestrutura From 53492c2f96bc45fdcba7eabcf5c3545577db4dc9 Mon Sep 17 00:00:00 2001 From: nitai Date: Tue, 13 Jan 2026 10:10:49 -0300 Subject: [PATCH 3/3] docs: atualiza trilhas de onboarding para nova arquitetura MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Atualiza setup-backend.md para usar data-platform e PostgreSQL - Atualiza roteiro-onboarding.md com novos repositórios - Atualiza setup-datascience.md com diagrama PostgreSQL - Remove referências aos repos arquivados (scraper, typesense) Co-Authored-By: Claude Opus 4.5 --- docs/onboarding/roteiro-onboarding.md | 20 ++- docs/onboarding/setup-backend.md | 239 +++++++++++++++++--------- docs/onboarding/setup-datascience.md | 19 +- 3 files changed, 185 insertions(+), 93 deletions(-) diff --git a/docs/onboarding/roteiro-onboarding.md b/docs/onboarding/roteiro-onboarding.md index f0cf9ff..81aab22 100644 --- a/docs/onboarding/roteiro-onboarding.md +++ b/docs/onboarding/roteiro-onboarding.md @@ -62,18 +62,18 @@ flowchart LR 3. [Componentes Estruturantes](../arquitetura/componentes-estruturantes.md) (20 min) 4. [Fluxo de Dados](../arquitetura/fluxo-de-dados.md) (20 min) 5. [Setup Backend](./setup-backend.md) (1-2 horas) -6. [Módulo Scraper](../modulos/scraper.md) (quando disponível) +6. [Data Platform](../modulos/data-platform.md) - Repositório unificado 7. [**Apache Airflow**](./airflow-tutorial.md) (3-5 horas) 8. [Primeiro PR](./primeiro-pr.md) (30 min) **Exercícios práticos:** 1. Rodar o scraper localmente para um órgão específico -2. Verificar os dados no HuggingFace após uma execução +2. Verificar os dados no PostgreSQL após uma execução 3. Adicionar uma nova fonte de dados (site gov.br) 4. Executar o pipeline de enriquecimento manualmente 5. Configurar ambiente Airflow local com Astro CLI -6. Criar primeira DAG de exemplo seguindo o tutorial +6. Executar sync do Typesense localmente --- @@ -238,11 +238,13 @@ Para uma formação completa com todos os exercícios e técnicas avançadas: | Repositório | Descrição | Quem Usa | |-------------|-----------|----------| -| [scraper](https://github.com/destaquesgovbr/scraper) | Pipeline de dados | Backend | -| [portal](https://github.com/destaquesgovbr/portal) | Portal web | Frontend | -| [typesense](https://github.com/destaquesgovbr/typesense) | Typesense local | Ambos | -| [infra](https://github.com/destaquesgovbr/infra) | Infraestrutura | DevOps | +| [data-platform](https://github.com/destaquesgovbr/data-platform) | Pipeline de dados (scraper, enrichment, sync) | Backend | +| [portal](https://github.com/destaquesgovbr/portal) | Portal web Next.js | Frontend | +| [infra](https://github.com/destaquesgovbr/infra) | Infraestrutura Terraform | DevOps | | [agencies](https://github.com/destaquesgovbr/agencies) | Dados de órgãos | Ambos | +| [docs](https://github.com/destaquesgovbr/docs) | Documentação | Todos | + +> **Nota**: Os repositórios `scraper` e `typesense` foram arquivados. O código foi movido para `data-platform`. --- @@ -272,13 +274,13 @@ Para uma formação completa com todos os exercícios e técnicas avançadas: - [ ] Rodei o scraper para um órgão específico - [ ] Entendi a integração com Cogfy -- [ ] Entendi como funciona o DatasetManager +- [ ] Entendi como funciona o PostgresManager +- [ ] Conectei ao PostgreSQL via Cloud SQL Proxy - [ ] Executei o workflow de testes - [ ] Configurei ambiente Airflow local (Astro CLI) - [ ] Criei minha primeira DAG - [ ] Completei pelo menos 3 exercícios do tutorial Airflow - [ ] Completei o [Tutorial Cloud Pub/Sub](./cloud-pubsub-tutorial.md) -- [ ] Publiquei e consumi mensagens no Pub/Sub ### Frontend Específico diff --git a/docs/onboarding/setup-backend.md b/docs/onboarding/setup-backend.md index 80ef626..bf36295 100644 --- a/docs/onboarding/setup-backend.md +++ b/docs/onboarding/setup-backend.md @@ -1,6 +1,6 @@ # Setup Backend (Python) -> Guia de configuração do ambiente para desenvolvedores Python trabalhando no scraper e pipeline de dados. +> Guia de configuração do ambiente para desenvolvedores Python trabalhando no data-platform e pipeline de dados. ## Pré-requisitos @@ -25,9 +25,9 @@ git --version # Git 2.40.x ou superior ## 1. Clonar Repositório ```bash -# Clone o repositório do scraper -git clone https://github.com/destaquesgovbr/scraper.git -cd scraper +# Clone o repositório data-platform +git clone https://github.com/destaquesgovbr/data-platform.git +cd data-platform ``` --- @@ -46,7 +46,7 @@ poetry shell ```bash # Deve mostrar a ajuda do CLI -python src/main.py --help +data-platform --help ``` --- @@ -63,103 +63,174 @@ Ou crie manualmente: ```bash # .env -HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx + +# PostgreSQL (fonte de verdade) +POSTGRES_HOST=localhost # ou IP do Cloud SQL via proxy +POSTGRES_PORT=5432 +POSTGRES_DB=destaquesgovbr +POSTGRES_USER=admin +POSTGRES_PASSWORD=xxxxx + +# Cogfy (enriquecimento) COGFY_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxx COGFY_COLLECTION_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + +# Embeddings API +EMBEDDINGS_API_URL=https://embeddings-xxx.run.app + +# Typesense +TYPESENSE_HOST=localhost +TYPESENSE_PORT=8108 +TYPESENSE_API_KEY=xyz + +# HuggingFace (opcional - para sync) +HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx ``` -### Obter tokens +### Obter credenciais -| Token | Onde obter | -|-------|------------| -| `HF_TOKEN` | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) - Criar token com permissão de escrita | +| Credencial | Onde obter | +|------------|------------| +| PostgreSQL | Solicitar ao tech lead (acesso via Cloud SQL Proxy) | | `COGFY_API_KEY` | Solicitar ao tech lead | | `COGFY_COLLECTION_ID` | Solicitar ao tech lead | - -> **Nota**: Para desenvolvimento local, você pode usar o dataset de staging ou criar um fork no HuggingFace. +| `EMBEDDINGS_API_URL` | Solicitar ao tech lead | +| `HF_TOKEN` | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) | --- ## 4. Estrutura do Projeto ``` -scraper/ -├── src/ -│ ├── main.py # CLI principal -│ ├── dataset_manager.py # Gerenciador HuggingFace -│ ├── cogfy_manager.py # Cliente Cogfy API -│ ├── upload_to_cogfy_manager.py # Upload para inferência -│ ├── enrichment_manager.py # Busca resultados Cogfy +data-platform/ +├── src/data_platform/ +│ ├── cli.py # CLI principal (Typer) +│ ├── managers/ +│ │ ├── postgres_manager.py # Acesso ao PostgreSQL +│ │ └── storage_adapter.py # Abstração de storage +│ ├── scrapers/ +│ │ ├── webscraper.py # Scraper genérico gov.br +│ │ ├── ebc_webscraper.py # Scraper EBC +│ │ ├── scrape_manager.py # Orquestração +│ │ ├── agencies.yaml # Mapeamento de órgãos +│ │ └── site_urls.yaml # URLs de raspagem +│ ├── cogfy/ +│ │ ├── cogfy_manager.py # Cliente Cogfy API +│ │ ├── upload_manager.py # Upload para inferência +│ │ └── enrichment_manager.py # Busca resultados Cogfy │ ├── enrichment/ -│ │ └── themes_tree.yaml # Árvore temática (25 temas) -│ └── scraper/ -│ ├── webscraper.py # Scraper genérico gov.br -│ ├── ebc_webscraper.py # Scraper EBC -│ ├── scrape_manager.py # Orquestração -│ ├── agencies.yaml # Mapeamento de órgãos -│ └── site_urls.yaml # URLs de raspagem -├── tests/ # Testes unitários -├── .github/workflows/ # GitHub Actions -├── pyproject.toml # Dependências Poetry -└── Dockerfile # Build Docker +│ │ └── themes_tree.yaml # Árvore temática (25 temas) +│ ├── typesense/ +│ │ ├── client.py # Cliente Typesense +│ │ └── indexer.py # Indexação de documentos +│ ├── jobs/ +│ │ ├── embeddings/ # Geração de embeddings +│ │ └── typesense/ # Sync com Typesense +│ └── dags/ # DAGs do Airflow +├── tests/ # Testes unitários +├── .github/workflows/ # GitHub Actions +├── pyproject.toml # Dependências Poetry +└── Dockerfile # Build Docker ``` --- -## 5. Executar o Scraper Localmente +## 5. Conectar ao PostgreSQL -### Scraping de um período específico +### Opção A: Cloud SQL Proxy (Desenvolvimento) ```bash -# Raspar notícias dos últimos 7 dias -python src/main.py scrape --start-date $(date -v-7d +%Y-%m-%d) --end-date $(date +%Y-%m-%d) +# Instalar Cloud SQL Proxy +# macOS +brew install cloud-sql-proxy -# Raspar período específico -python src/main.py scrape --start-date 2024-12-01 --end-date 2024-12-03 +# Autenticar +gcloud auth application-default login + +# Iniciar proxy +cloud_sql_proxy -instances=PROJECT_ID:southamerica-east1:destaquesgovbr-postgres=tcp:5432 +``` + +### Opção B: Túnel SSH (Via Dev VM) + +```bash +# SSH com túnel +gcloud compute ssh devvm --zone=southamerica-east1-a -- -L 5432:10.x.x.x:5432 ``` -### Scraping de um órgão específico +### Verificar conexão ```bash -# Editar site_urls.yaml para ter apenas o órgão desejado -# Ou usar filtros no código (ver webscraper.py) +# Via psql +psql -h localhost -U admin -d destaquesgovbr + +# Via Python +python -c "from src.data_platform.managers.postgres_manager import PostgresManager; pm = PostgresManager(); print(pm.get_news_count())" +``` + +--- + +## 6. Executar o Scraper Localmente + +### Scraping de um período específico + +```bash +# Raspar notícias dos últimos 7 dias +data-platform scrape --start-date $(date -v-7d +%Y-%m-%d) --end-date $(date +%Y-%m-%d) + +# Raspar período específico +data-platform scrape --start-date 2024-12-01 --end-date 2024-12-03 ``` ### Scraping EBC ```bash -python src/main.py scrape-ebc --start-date 2024-12-01 --end-date 2024-12-03 +data-platform scrape-ebc --start-date 2024-12-01 --end-date 2024-12-03 --allow-update ``` --- -## 6. Testar Integração Cogfy +## 7. Testar Integração Cogfy ### Upload para Cogfy ```bash -python src/upload_to_cogfy_manager.py --start-date 2024-12-01 --end-date 2024-12-03 +data-platform upload-cogfy --start-date 2024-12-01 --end-date 2024-12-03 ``` ### Buscar enriquecimento ```bash # Aguardar ~20 minutos após upload -python src/enrichment_manager.py --start-date 2024-12-01 --end-date 2024-12-03 +data-platform enrich --start-date 2024-12-01 --end-date 2024-12-03 ``` --- -## 7. Setup Typesense Local (Opcional) +## 8. Setup Typesense Local -Para testar a indexação localmente: +Para testar a busca localmente: ```bash -# Clone o repositório do Typesense local -git clone https://github.com/destaquesgovbr/typesense.git -cd typesense - -# Subir Typesense com Docker +# Criar docker-compose.yml para Typesense +cat > docker-compose.yml << 'EOF' +version: '3.8' +services: + typesense: + image: typesense/typesense:0.25.2 + container_name: typesense + ports: + - "8108:8108" + environment: + - TYPESENSE_API_KEY=xyz + - TYPESENSE_DATA_DIR=/data + volumes: + - typesense-data:/data +volumes: + typesense-data: +EOF + +# Subir Typesense docker compose up -d # Verificar se está rodando @@ -169,13 +240,13 @@ curl http://localhost:8108/health ### Carregar dados no Typesense ```bash -# No repositório typesense -python python/scripts/load_data.py --mode incremental --days 7 +# Sync do PostgreSQL para Typesense +data-platform sync-typesense --start-date $(date -v-7d +%Y-%m-%d) ``` --- -## 8. Executar Testes +## 9. Executar Testes ```bash # Executar todos os testes @@ -190,7 +261,7 @@ poetry run pytest tests/test_webscraper.py -v --- -## 9. Linting e Formatação +## 10. Linting e Formatação ```bash # Formatação com Black @@ -205,52 +276,60 @@ poetry run mypy src/ --- -## 10. Build Docker +## 11. Build Docker ```bash # Build da imagem -docker build -t scraper . +docker build -t data-platform . # Executar container -docker run --env-file .env scraper python src/main.py --help +docker run --env-file .env data-platform data-platform --help ``` --- ## Comandos Úteis -### CLI do Scraper +### CLI do data-platform ```bash # Ver ajuda -python src/main.py --help +data-platform --help # Scraping gov.br -python src/main.py scrape --start-date YYYY-MM-DD --end-date YYYY-MM-DD +data-platform scrape --start-date YYYY-MM-DD --end-date YYYY-MM-DD # Scraping EBC -python src/main.py scrape-ebc --start-date YYYY-MM-DD --end-date YYYY-MM-DD --allow-update +data-platform scrape-ebc --start-date YYYY-MM-DD --end-date YYYY-MM-DD --allow-update # Upload para Cogfy -python src/upload_to_cogfy_manager.py --start-date YYYY-MM-DD --end-date YYYY-MM-DD +data-platform upload-cogfy --start-date YYYY-MM-DD --end-date YYYY-MM-DD # Enriquecimento -python src/enrichment_manager.py --start-date YYYY-MM-DD --end-date YYYY-MM-DD +data-platform enrich --start-date YYYY-MM-DD --end-date YYYY-MM-DD + +# Gerar embeddings +data-platform generate-embeddings --start-date YYYY-MM-DD + +# Sync Typesense +data-platform sync-typesense --start-date YYYY-MM-DD ``` -### Interação com Dataset +### Interação com PostgreSQL ```python # Python interativo -from src.dataset_manager import DatasetManager +from src.data_platform.managers.postgres_manager import PostgresManager -dm = DatasetManager() -df = dm.load_dataset() -print(f"Total de documentos: {len(df)}") +pm = PostgresManager() -# Filtrar por agência -gestao_df = df[df['agency'] == 'gestao'] -print(f"Notícias do MGI: {len(gestao_df)}") +# Contar notícias +print(f"Total de notícias: {pm.get_news_count()}") + +# Buscar por agência +gestao_news = pm.get_news_by_agency("gestao", limit=10) +for news in gestao_news: + print(f"{news['published_at']}: {news['title']}") ``` --- @@ -259,9 +338,9 @@ print(f"Notícias do MGI: {len(gestao_df)}") | Arquivo | Descrição | Quando Modificar | |---------|-----------|------------------| -| `src/scraper/site_urls.yaml` | URLs de raspagem | Adicionar novo órgão | -| `src/scraper/agencies.yaml` | Mapeamento ID → Nome | Novo órgão | -| `src/enrichment/themes_tree.yaml` | Árvore temática | Novo tema/subtema | +| `src/data_platform/scrapers/site_urls.yaml` | URLs de raspagem | Adicionar novo órgão | +| `src/data_platform/scrapers/agencies.yaml` | Mapeamento ID → Nome | Novo órgão | +| `src/data_platform/enrichment/themes_tree.yaml` | Árvore temática | Novo tema/subtema | | `pyproject.toml` | Dependências | Nova biblioteca | | `.github/workflows/main-workflow.yaml` | Pipeline diário | Alterar pipeline | @@ -286,11 +365,15 @@ flowchart TD ## Troubleshooting -### Erro de autenticação HuggingFace +### Erro de conexão com PostgreSQL ```bash -# Fazer login manualmente -huggingface-cli login +# Verificar se proxy está rodando +ps aux | grep cloud_sql_proxy + +# Reiniciar proxy +pkill cloud_sql_proxy +cloud_sql_proxy -instances=PROJECT_ID:southamerica-east1:destaquesgovbr-postgres=tcp:5432 ``` ### Erro de conexão com Cogfy @@ -316,7 +399,7 @@ poetry install --sync ## Próximos Passos 1. Leia o código do `webscraper.py` para entender o scraping -2. Explore o `dataset_manager.py` para entender a integração com HuggingFace +2. Explore o `postgres_manager.py` para entender o acesso aos dados 3. Execute o pipeline completo localmente 4. Escolha uma issue para trabalhar diff --git a/docs/onboarding/setup-datascience.md b/docs/onboarding/setup-datascience.md index cc8d1bd..8d58e97 100644 --- a/docs/onboarding/setup-datascience.md +++ b/docs/onboarding/setup-datascience.md @@ -11,19 +11,26 @@ Este módulo prepara seu ambiente para análise de dados do projeto DestaquesGov ```mermaid flowchart LR subgraph "Pipeline DestaquesGovBr" - A[Scraping] --> B[Enriquecimento LLM/Cogfy] - B --> C[HuggingFace Dataset] - C --> D[Typesense] + A[Scraping] --> B[(PostgreSQL)] + B --> C[Enriquecimento LLM/Cogfy] + C --> B + B --> D[Typesense] D --> E[Portal Web] + B -.->|DAG Airflow| F[(HuggingFace)] end subgraph "Seu Ambiente" - F[Python + Poetry] --> G[VSCode + Notebooks] - G --> H[Análise de Dados] - C -.->|datasets lib| H + G[Python + Poetry] --> H[VSCode + Notebooks] + H --> I[Análise de Dados] + F -.->|datasets lib| I end ``` +!!! info "Fonte de Dados" + O **PostgreSQL** é a fonte de verdade central do sistema. O **HuggingFace** é sincronizado diariamente e serve como camada de distribuição de dados abertos. + + Para análises exploratórias e Data Science, o dataset no HuggingFace é a forma mais prática de acessar os dados. + --- ## Pré-requisitos