Skip to content

mopanc/gov-analytics

Repository files navigation

GOV.analytics

Plataforma de análise de despesa pública em Portugal, baseada em dados abertos de contratos públicos (Portal BASE/IMPIC via dados.gov.pt).

Stack

Componente Tecnologia
Backend API Node.js + TypeScript + Fastify
Query Builder Kysely (type-safe, zero-overhead)
Base de Dados PostgreSQL (partições por ano, materialized views)
Queue System Redis + BullMQ
Frontend Next.js 15 (App Router) + Tailwind CSS v4
Monorepo npm workspaces + Turborepo

Pré-requisitos

Antes de começar, certifica-te que tens instalado:

  • Node.js >= 20 (node -v)
  • PostgreSQL >= 15 (psql --version)
  • Redis >= 7 (redis-server --version)

Instalar no macOS (Homebrew)

brew install node@20 postgresql@15 redis

Iniciar os serviços

# Iniciar PostgreSQL
brew services start postgresql@15

# Iniciar Redis
brew services start redis

Setup passo-a-passo

1. Instalar dependências

cd ~/Desktop/gov
npm install

2. Criar a base de dados

# Criar o utilizador e a base de dados
psql postgres -c "CREATE USER gov WITH PASSWORD 'gov';"
psql postgres -c "CREATE DATABASE gov OWNER gov;"
psql postgres -c "ALTER USER gov CREATEDB;"

3. Configurar variáveis de ambiente

cp .env.example .env

Editar .env se necessário (os valores por defeito devem funcionar para desenvolvimento local):

DATABASE_URL=postgresql://gov:gov@localhost:5432/gov
REDIS_URL=redis://localhost:6379
API_PORT=3001
LOG_LEVEL=info
DATA_DIR=./data/raw

4. Correr as migrations

Isto cria todos os schemas, tabelas, indexes, partições e materialized views:

npm run db:migrate -w @gov/api

Resultado esperado:

  OK    001_create_schemas.sql
  OK    002_create_meta_tables.sql
  OK    003_create_staging_tables.sql
  OK    004_create_core_tables.sql
  OK    005_create_indexes.sql
  OK    006_create_materialized_views.sql

Migrations complete: 6 applied, 0 skipped.

5. Ingerir dados reais

Agora o passo importante — carregar contratos públicos reais do portal dados.gov.pt.

Opção A: Carregar um único ano (rápido, para testar)

# Carregar contratos de 2026 (~27k registos, ~45MB JSON)
npm run ingest:year -w @gov/api 2026

Tempo estimado: 2-5 minutos (depende da internet e do hardware).

Opção B: Carregar todos os anos (2018-2026)

# Carregar todos os anos disponíveis (~200k+ registos)
npm run ingest -w @gov/api

Tempo estimado: 15-45 minutos (download de ficheiros XLSX grandes + parsing).

O progresso é mostrado no terminal:

INFO: Phase 1: Downloading...
INFO: Phase 2: Parsing file...
INFO: Phase 3: Transforming and loading...
INFO: Batch progress  progress="5000/27484" inserted=4823
INFO: Batch progress  progress="10000/27484" inserted=9647
...
INFO: Ingestion completed  rowsInserted=26891 rowsDuplicates=0
INFO: Refreshing materialized views...
INFO: All materialized views refreshed

6. Iniciar a API

npm run dev:api

A API fica disponível em http://localhost:3001. Testa:

# Health check
curl http://localhost:3001/health

# Ver sumário
curl http://localhost:3001/api/analytics/summary

# Listar contratos
curl "http://localhost:3001/api/contracts?page=1&limit=5"

# Top fornecedores
curl http://localhost:3001/api/analytics/top-suppliers

# Pesquisar fornecedores
curl "http://localhost:3001/api/suppliers?search=Portugal+Telecom"

7. Iniciar o frontend

Num terminal separado:

npm run dev:web

Abre http://localhost:3000 no browser.

Comandos disponíveis

Comando Descrição
npm run dev:api Iniciar API em modo development (hot reload)
npm run dev:web Iniciar frontend Next.js (hot reload)
npm run dev Iniciar tudo (API + Web) via Turborepo
npm run db:migrate -w @gov/api Correr migrations da base de dados
npm run ingest -w @gov/api Ingerir todos os anos de contratos
npm run ingest:year -w @gov/api 2026 Ingerir um ano específico
npm run scheduler -w @gov/api Iniciar scheduler de ingestão automática
npm run build Build de produção (API + Web)

Estrutura do projecto

gov/
├── apps/
│   ├── api/                    # Backend (Fastify + Kysely + BullMQ)
│   │   └── src/
│   │       ├── config/         # Database, Redis, queues, data sources
│   │       ├── modules/        # Contracts, Suppliers, Analytics (routers + repos)
│   │       ├── ingestion/      # ETL pipeline (downloader, parser, file reader)
│   │       ├── scheduler/      # Cron jobs
│   │       ├── scripts/        # Manual ingestion triggers
│   │       ├── api/            # Fastify server + middleware
│   │       ├── shared/         # Logger, errors, types
│   │       └── db/migrations/  # SQL migrations
│   │
│   └── web/                    # Frontend (Next.js 15 + Tailwind v4)
│       └── src/
│           ├── app/            # Pages (Dashboard, Contracts, Suppliers, Analytics)
│           └── lib/            # API client, formatters
│
├── packages/
│   └── shared-types/           # TypeScript types partilhados API ↔ Web
│
└── data/raw/                   # Ficheiros descarregados (gitignored)

Fontes de dados

Fonte URL Formato Frequência
Contratos Públicos (IMPIC) dados.gov.pt JSON/XLSX por ano Quinzenal

API Endpoints

Contratos

  • GET /api/contracts — Lista paginada (filtros: year, entityId, procedureType, contractType, search)
  • GET /api/contracts/:id — Detalhe de contrato (inclui fornecedores)

Fornecedores

  • GET /api/suppliers — Lista paginada (filtros: search, country)
  • GET /api/suppliers/:id — Detalhe de fornecedor
  • GET /api/suppliers/:id/contracts — Contratos de um fornecedor

Analytics

  • GET /api/analytics/summary — Totais gerais (contratos, valor, fornecedores, entidades)
  • GET /api/analytics/top-suppliers — Top fornecedores por valor total
  • GET /api/analytics/yearly-breakdown — Breakdown anual por tipo de procedimento
  • GET /api/analytics/monthly-timeseries — Série temporal mensal
  • GET /api/analytics/entity-concentration — Índice HHI de concentração por entidade

Modelo de dados

A base de dados usa 4 schemas:

  • meta — Controlo do pipeline (ingestion_runs, source_files)
  • staging — Buffer temporário durante ingestão
  • public — Dados normalizados finais (entities, suppliers, contracts, budget_executions)
  • analytics — Materialized views pré-computadas para dashboard

As tabelas contracts e budget_executions são particionadas por ano para performance analítica.

Troubleshooting

"relation does not exist"

As migrations não foram aplicadas. Corre:

npm run db:migrate -w @gov/api

"connection refused" (PostgreSQL)

Verifica que o PostgreSQL está a correr:

brew services list | grep postgresql
pg_isready

"connection refused" (Redis)

Verifica que o Redis está a correr:

brew services list | grep redis
redis-cli ping  # deve responder PONG

Materialized views vazias

As views só têm dados após a primeira ingestão. Corre:

npm run ingest:year -w @gov/api 2026

About

Public spending analysis platform for Portugal — open data from Portal BASE/IMPIC

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors