Transparência técnica

Como os dados chegam
até você.

Cada número neste site foi extraído de uma fonte oficial de imigração por um pipeline automático. Esta página explica exatamente como.

Pipeline de extração

Do site oficial ao JSON em 4 etapas

01

GitHub Actions dispara o cron

No dia 1 de cada mês às 06:00 UTC, o workflow monthly-update.yml inicia. Nenhuma intervenção manual é necessária. O histórico de execuções fica visível na aba Actions do repositório.

cron: '0 6 1 * *' → Ubuntu latest
02

Fetcher visita as URLs oficiais

Para cada país, o fetcher acessa as URLs declaradas em src/sources/{cc}.ts usando fetch nativo do Bun. Sites que exigem JavaScript recebem fallback via Playwright headless. Mínimo de 2 segundos entre requests no mesmo domínio.

fetch nativo → Playwright (fallback JS-heavy) → HTML bruto
03

LLM extrai dados estruturados

O HTML é processado pelo @mozilla/readability para isolar o conteúdo principal. O texto limpo é enviado para a API da Anthropic. Claude Haiku 4.5 processa a maioria das URLs. URLs marcadas como críticas usam Claude Sonnet 4.5. O output é um JSON seguindo o schema declarado.

Haiku 4.5 (80%) → Sonnet 4.5 (20% URLs críticas)
04

Zod valida, travas verificam, Git versionia

Cada campo do JSON é validado contra o schema Zod antes de qualquer escrita. Depois, a reconciliação de IDs mapeia cada visto extraído para seu ID canônico do ciclo anterior (evita que nomes ligeiramente diferentes criem duplicatas). O guard detecta degradação estrutural e aborta o commit se o resultado piorou. O audit compara valores críticos contra os verificationUrls oficiais e anota divergências maiores que 5%. Só então o snapshot vai para data/current/ e o site é gerado.

reconcile-ids → guard (bloqueia) → audit → site:generate → data/history/
.github/workflows/monthly-update.yml
 1on:
 2  schedule:
 3    # Dia 1 de cada mês, 06:00 UTC
 4    - cron: '0 6 1 * *'
 5  workflow_dispatch: # disparo manual
 6
 7steps:
 8  - bun run validate      # valida JSON atual antes
 9  - bun run extract       # extrai todos os países
10  - bun run patch:all     # reaplica enriquecimentos manuais
11  - bun run validate      # valida resultado extraído
12  - bun run guard         # trava: aborta se extração degradou
13  - bun run audit         # auditoria: compara vs verificationUrls
14  - bun run index         # gera index.json
15  - bun run site:generate # gera as páginas HTML
16  - bun run diff          # detecta mudanças significativas
17
18  # Commit e deploy ocorrem somente se guard passou
19  - uses: peaceiris/actions-gh-pages@v4
20    with:
21      publish_dir: ./data/current
22      publish_branch: gh-pages
Contrato de dados

O que cada JSON contém

Todos os 10 países seguem o mesmo schema. Os campos são validados pelo Zod antes de qualquer publicação. Se um campo obrigatório vier vazio ou com tipo errado, o snapshot inteiro é rejeitado.

data/current/nl.json (resumido)
 1{
 2  "meta": {
 3    "country": "nl",
 4    "countryName": "Países Baixos",
 5    "lastUpdated": "2026-06-01T06:00:00Z"
 6  },
 7  "visaTypes": [{
 8    "id": "highly-skilled-migrant",
 9    "name": "Altamente Qualificado",
10    "requirements": {
11      "incomeRequirement": {
12        "amount": 5688,
13        "currency": "EUR",
14        "period": "monthly"
15      }
16    }
17  }],
18  "reliability": {
19    "extractedBy": "llm",
20    "extractionConfidence": "high",
21    "humanReviewedAt": null,
22    "knownIssues": []
23  }
24}
src/extractors/schema.ts (campos principais)
 1const MoneyAmountSchema = z.object({
 2  amount:   z.number().nonnegative(),
 3  currency: z.enum(['EUR','USD','BRL','AUD']),
 4  period:   z.enum(['one-time','monthly','yearly','total'])
 5              .nullable().optional(),
 6})
 7
 8const ReliabilitySchema = z.object({
 9  extractedBy:          z.enum(['llm','manual']),
10  extractionConfidence: z.enum(['high','medium','low']),
11  humanReviewedAt:      z.string().datetime().nullable(),
12  knownIssues:          z.array(z.string()),
13})
14
15const CountryDataSchema = z.object({
16  meta:                MetaSchema,
17  visaTypes:          z.array(VisaTypeSchema),
18  generalRequirements: GeneralRequirementsSchema,
19  reliability:         ReliabilitySchema,
20})
Stack técnico

Tecnologias e por que cada uma

Cada decisão foi tomada para minimizar infraestrutura, maximizar confiabilidade e manter o custo mensal abaixo de USD 1.

Runtime
Bun 1.1+

Fetch nativo sem polyfill, runtime de testes embutido, instalação única. Compatível com Node 20+ se necessário.

Linguagem
TypeScript estrito

Schema tipado impede campos ausentes silenciosos. O tsc --noEmit roda no CI antes de qualquer extração.

LLM
Anthropic API — estratégia 80/20

Haiku 4.5 para a maioria das URLs (barato, rápido). Sonnet 4.5 apenas para URLs marcadas como críticas. Custo estimado: USD 4 a 10 por ano.

Validação
Zod

Schema declarativo colocado em src/extractors/schema.ts. Falha de parse lança exceção e impede publicação de dados inválidos.

Storage
JSON versionado no Git

Zero infraestrutura. O histórico mensal é o próprio data/history/. Diff visual no GitHub. Qualquer um pode consumir via CDN sem autenticação.

CI
GitHub Actions

Free tier cobre folgadamente um cron mensal. O log de cada extração fica arquivado por 90 dias na aba Actions.

Dados abertos

Como consumir o JSON

Os snapshots ficam disponíveis publicamente via GitHub Pages, com CORS aberto. Nenhuma chave de API necessária.

Endpoints disponíveis
# País específico
https://vl-builds.github.io/rota-legal-monitor/nl.json
https://vl-builds.github.io/rota-legal-monitor/pt.json
https://vl-builds.github.io/rota-legal-monitor/de.json

# Índice de todos os países
https://vl-builds.github.io/rota-legal-monitor/index.json

# Países disponíveis: nl pt de es ie it fr be at au

O código é aberto

Inspecione o pipeline, proponha melhorias ou adicione um novo país. Todo o processo está no repositório público.