Skip to content

O pipeline de dados

O Futuros não consulta APIs ao vivo quando alguém abre uma página. Todo o corpus é ingerido, baked e congelado como JSON estático antes do deploy; a SPA só lê arquivos. Isso torna a plataforma rápida, cacheável, capaz de operar offline — e, sobretudo, auditável: cada cifra que você vê foi verificada em tempo de build contra seu registro de fonte. Este pipeline serve diretamente ao Pilar 1 — Fundo Fiduciário de Dados: um corpus com proveniência e licença limpa é o único ativo que um modelo regional não pode copiar.

O registro de fontes

Cada fonte externa é um SourceRecord declarado em scripts/sources/registry/. Ele define de onde sai o dado e sob quais regras pode ser publicado:

  • AdapterEngine — o tipo de conector: series, bulk, sdmx, ckan, ocds, event, entity, microdata, provider ou meta. Cada API externa tem um único adaptador em adapters.ts (WHO GHO, UNESCO UIS, OpenAlex, Climate Watch, UN SDG, UNHCR, OWID, IMF DataMapper, SDMX/DBnomics genérico…). As ondas posteriores vivem em scripts/sources/gap/ (um arquivo por fonte). A onda F (wave-f/ + src/lib/gap-sources-f/) é o passe de granularidade plena: emaciação JME, matrícula terciária UIS, nutrição DHS, espaço cívico CIVICUS, preenchimento Ember/IRENA, e sondas de lacuna honesta (RICYT, ILIA/GIRAI/GDB, INE.Stat saúde/inovação do Chile, ENDI) que re-verificam e devolvem []. Atualização: scripts/refresh-wave-f.ts.
  • LicensePosturekind / redistribute / aggregates_only: o que pode ser republicado e o que só pode ser citado.
  • Verificação por sondasources.ts é o SOURCE_REGISTRY_V2, com endpoints testados; regulation-sources.ts é o registro irmão para o rastreio de leis de IA. Os nso/*.json são 27 listas de fontes por instituto nacional de estatística.
  • Anatomia de verificação — cada registro declara ainda seu deep_link (a URL por-registro que é estampada em citations.json), sua cadência (cadence_days), um spec verify de re-verificação por amostragem (refetch-compare: pedir de novo uma amostra e comparar com o que foi salvo) e um expected_volume dimensionado para um sentinela de volume de ±50%. Atenção: o sentinela e o re-verificador são hoje contrato declarado do esquema — nenhum ingestor os executa ainda; o guardião que de fato roda a cada refresh é o guardião de frescor.

O esquema completo de um SourceRecord (scripts/sources/registry/schema.ts) — cada campo com a regra que validateRegistry() aplica a ele:

CampoTipo / valoresRegra em validateRegistry()
keyUPPER_SNAKE estávelregex ^[A-Z0-9_]+$; duplicado ou inválido = erro
tier · priority · statusprimary/secondary/curated · P0/P1/P2 · wired/ready/blocked/researcho tick ingest:due do daemon drena linhas ready
engineum dos 10 AdapterEngineengine desconhecido = erro
licensekind (open/cc-by/non-commercial/share-alike/cite-only) + redistribute + aggregates_only?o gate de licença (abaixo); aggregates_only + engine microdata = warn de lembrete: o adaptador agrega, nunca despeja linhas
authnone/env_key/registration/approval + envenv = variável de ambiente que o adaptador lê
endpointpadrão de URL com placeholders {iso3}/{code}ausente = erro
deep_linkpadrão de URL por-registroé o que se estampa em citations.json
cadence_daysnúmeroo ledger de frescor compara contra isto
geos · geo_levelISO3[] ou "ALL" · country/adm1/adm2/point/supranationalgeo fora do conjunto de 26 (25 países + LATAM) = warn
pillarsslugs de dimensãovazio com engine ≠ provider = warn
expected_volumenúmero ≥ 0negativo ou não numérico = erro; dimensiona o sentinela de ±50%
verifysample_n + method (refetch-compare/liveness-only/checksum)bloco ausente ou sample_n < 0 = erro

Os adaptadores são isolados por falha: um endpoint morto devolve [] em vez de quebrar a ingestão. A cadência (de quantos em quantos meses cada indicador é atualizado) vive junto à fonte e se materializa no ledger de frescor.

O gate de licença (em tempo de compilação)

A regra mais importante do registro não é de runtime: é de compilação. validateRegistry() rejeita qualquer fonte cuja licença diga redistribute: false mas esteja ligada a um engine que não seja provider. A condição exata é tripla: redistribute === false e engine ≠ provider e status === "wired" — uma fonte cite-only pode ficar declarada como research ou blocked sem quebrar o build; o que ela não pode é estar ligada a um engine que produz valores. Republicar valores de uma fonte que só permite citar é um erro de compilação (assertRegistryValid() lança com a lista de chaves ofensoras), não um aviso que alguém poderia ignorar. A postura de licença de uma fonte decide se seus dados aparecem como indicador republicado, só como agregado, ou só como citação — e decide isso antes de o build produzir um único arquivo.

Esse mesmo gate se aplica aos dados aportados por terceiros via /contribuir: nada é redistribuído além do que sua licença permite. É o mecanismo que mantém honesto o Fundo Fiduciário de Dados.

As etapas de ingestão

scripts/ingest-all.ts orquestra três etapas, cada uma isolada por falha (uma etapa que falha não arrasta as outras):

  1. runMultisource() → células de parameter-cache/ + citações. É o grosso: o valor de cada indicador por país, com seu citation_id.
  2. runDocuments()documents/ e news/: documentos e notícias por dimensão e país.
  3. runSignals() → sinais citados, extraídos por LLM, em signals/.

Ao terminar, escreve refresh-meta.json. Há ingestores de domínio adicionais para orçamento aberto, liberdade econômica, liberdade digital, deriva autoritária, OGP, liberdade humana, liberdade de imprensa, regulação, observatório e o nível subnacional ADM1.

Existe ainda uma quarta via com cadência própria: o pulso social. ingest-social.ts (eventos GDELT + GKG + RSS social) e fetch-trends.ts alimentam as famílias social/, social-trends/, attention/ e civic/, com seus próprios bakes (bake-trends, bake-civic, bake-attention). Aparece em refresh-meta.json como a fonte social:gdelt, com contagem de posts.

E uma quinta via: alta frequência (HF). ingest-hf.ts roda NASA POWER diariamente (um ponto capital por ISO3) e, na mesma execução, adaptadores de FIRMS, ClimateSERV CHIRPS, Ember mensal, Wikimedia, GHS, FAO ASIS, GFW e Meta Movement. A regra de publicação é estrita: apenas nasa-power.json é commitado e servido. Um armazém vazio (observations: []) ou um feed sem linhas publicáveis não é escrito sob public/data/hf-observations/ — um 200 oco é um defeito, não um "em breve". check-hf-publish.ts bloqueia o build se aparecer qualquer JSON extra ou um POWER vazio. POWER não entra em parameter-cache nem em observations.csv (esse CSV já ultrapassa o teto de 100 MB do GitHub). O lane hf não move generated_at.

O bake

scripts/bake-all.ts transforma o cache bruto nos datasets derivados que as páginas consomem: narrativas → indicadores de fronteira → subnacional → briefs e contradições de pulso → previsões → shadows → grafo de personas → escores compostos → índice de mercados → _index.json. São 41 scripts bake-*.ts mais 10 geradores build-* — perto de 50 scripts de bake e geração: escores, mercados, V-Dem, exploração, insights, saúde do dado, sinais, coalizões, ontologia, tendências, cívica, atenção, feeds, contribuições, soberania de cômputo, convergência regional, nowcast de instabilidade, triangulação, narrativas e grafo de personas, além de geradores como build-democracy-index e build-search-index.

Dois geradores de domínio indexam famílias que você já viu na lista de datasets: build-cases-index.ts regenera o _index.json de cases/ (os precedentes e aprendizados por país, autorados arquivo a arquivo) e gen-elections-index.mjs o de elections/ (o calendário eleitoral regional).

Nenhum fato é "baked" dentro de um modelo: os fatos vivem nos dados com sua citação determinista; o bake só pré-calcula o que é derivável deles.

A API pública baked

O pipeline tem um quarto produto além dos dados da SPA: uma API estática somente-leitura em public/api/v1, re-baked a cada deploy por bake-api.tscitations.csv/.json, freshness.json, contributions.json, futuros-data.xlsx e registros por país e por indicador com interoperabilidade M49/SDMX. Ela tem seu próprio gate: check-api.ts roda depois do bake e verifica que a API espelha os dados atuais (por exemplo, que a contagem de citações do seu índice coincide com citations.json). Por construção, a API nunca fica defasada do corpus. Referência completa em Public API.

As famílias de datasets (public/data/**)

O resultado são 68 famílias de dados estáticos. Entre as principais:

  • parameters.json (os 10 eixos), geographies.json (25 países + LATAM, com ISO3; os códigos M49 não vivem aqui — são unidos a partir de um mapeamento ISO3→M49 próprio de bake-api.ts ao fazer o bake da API pública).
  • parameter-cache/ (células indicador×país) e parameter-cache-adm1/ (milhares de células subnacionais).
  • hf-observations/ — só nasa-power.json é publicado (NASA POWER não vazio). Os outros feeds HF são ingeridos mas não commitados nem servidos; um arquivo oco é um defeito.
  • citations.json — o registro central de citações (vários MB).
  • documents/, news/, signals/, social/, social-trends/, attention/, shadows/.
  • personas/ (perfis + _index + _graph), state-history/, country-history/.
  • scores.json (composto), data-health.json, frontier-indicators/, compute-sovereignty/, agi-economy/, dependency/, climate-action/.
  • pulse/, regional-convergence/, subnational-convergence/, instability-nowcast/, triangulation/, uncertainty/, insights/.
  • pilots/, pilot-archetypes/, financing-instruments/, coalitions/, positions/, cases/.
  • regulation/, constitutional/, elections/, democracy/, transparency/, ai-governance/.
  • markets/, metrics/, ontology/, civic/, gender-gap/, trends/.
  • search-index.json (índice Voyage para o assistente), catalog.json, sdg-crosswalk.json, falsifications.json.

Frescor

public/data/refresh-meta.json é o ledger de frescor, com duas granularidades: o mapa indicators rastreia por-indicador o subconjunto Banco Mundial / WGI (49 códigos); todo o resto (884 séries no ledger de fontes assado) é rastreado em nível de feed no mapa sources (197 entradas como social:gdelt ou doc:exa, com kind, last_refreshed, next_refresh_due e contagens como posts). A forma de uma entrada por-indicador:

CampoExemplo (SI.POV.GINI)O que responde
source / source_urlWorld Bank Open Datade onde saiu?
cadence_months / next_refresh_due12 / 2027-03-02quando toca atualizar?
last_refreshed2026-07-28quando pedimos pela última vez?
last_observation_year2025quão novo é o dado em si?
countries_with_data / total_countries22 / 25quanta cobertura?
source_frontier_year / source_frontier_checked_at2025 / 2026-07-23existe algo mais novo no upstream?
freshness_status / freshness_notecurrentveredicto: current, source_not_updated ou no_data

A distinção que carrega o peso é last_refreshed vs last_observation_year: um pull de ontem com dado de 2023 não é uma falha do pipeline, e sim atraso do provedor — e source_frontier_year o demonstra, porque registra o ano mais novo que o upstream publica. É o que torna possível mostrar o vintage de uma cifra em vez de fingir que tudo está em dia. A superfície /confianza lê este ledger; /incertidumbre é outra coisa — expõe bandas de confiança do V-Dem a partir de uncertainty/vdem-ci.json, não este arquivo.

O ledger tem ainda um guardião ativo, não só uma tela. O runner multisource poda e reescreve células ao ingerir, então uma execução com um adaptador caído ou limitado poderia apagar em silêncio células que existiam — o defeito "meu fetch falhou, escrito em disco como não há nada". freshness-guard.py impede isso com três subcomandos sobre parameter-cache/:

snapshot <out.json>          # antes da execução
  para cada parameter-cache/<pilar>__<ISO3>.json, para cada indicador:
    snap[id][celda] = { vintage: vintage_year, value, obj: <indicador completo> }

compare <before.json>        # depois da execução
  por indicador:  gone       (células que desapareceram)
                  regressed  (vintage retrocedeu: after < before)
                  advanced   (vintage avançou)  ·  added (células novas)
  reporta o total de células perdidas + regredidas

repair <before.json> --apply
  restaura SOMENTE gone ∪ regressed, a partir do obj salvo no snapshot
  advanced e added ficam intactos — um avanço genuíno nunca é revertido
  sem --apply é dry run: lista o que restauraria, não toca o disco

Invariante sutil do filtro --only=<substr>: o guardião filtra a linha de base com os mesmos termos que o snapshot vivo. Sem isso, todo indicador fora do filtro pareceria gone e o repair restauraria a plataforma inteira à linha de base — incluídas correções deliberadas posteriores. As execuções de refresh são não destrutivas por construção.

No prebuild, bake-source-ledger.ts faz o bake do ledger de fontes que alimenta /confianza e estampa em refresh-meta.json os campos de nível superior source_ledger_baked_at e source_ledger_series; nos refreshes de dados, recheck-wb-freshness.ts re-verifica os vintages do Banco Mundial contra o upstream e estampa wb_freshness_checked_at.

Os gates do prebuild

O prebuild é uma cadeia de gates encadeados com && no package.json; se qualquer um falha, o build falha e nada é implantado:

* = consultiva (--warn): reporta e sai 0.

Primeiro o terreno: dois typechecks (tsc -p tsconfig.scripts.json para os scripts de ingestão e bake, tsc -p tsconfig.server.json para o servidor do assistente e as funções), a suíte completa de testes (bun test --timeout 15000 — 2.076 testes em 175 arquivos, incluindo o servidor do assistente, o handler MCP e os bakers) e build-positions-index --check. Depois, vinte e uma verificações check-* com nome próprio, mais dois gates em modo --check sobre scripts que normalmente escrevem (build-positions-index, bake-trends-index). Três são consultivas (--warn: reportam e saem 0 — check-search-index, check-vintage, check-adm1-coverage); as demais fazem o build falhar.

Proveniência e coerência do corpus:

  1. check-traced — toda cifra tem seu rastro até a fonte.
  2. check-signals — os sinais estão bem formados e citados.
  3. check-source-links — os links de citação apontam para o registro específico de país + indicador. A única exceção é a lista LANDING_OK dentro do próprio gate: um opt-out documentado por fonte (camadas curadas e internas que resolvem para /metodologia, camadas documentais EXA/GDELT/RSS/diários oficiais/sinais e umas poucas fontes sem URL estável por registro cujo destino honesto é sua própria página de dataset), cada um com sua justificativa no código. Todo o resto leva deep link ou o build falha.
  4. check-pilots — coerência dos pilotos.
  5. check-persona-scopenenhum perfil pode ficar inalcançável. O <select> de país de /personas é construído a partir dos 25 canônicos, então um perfil arquivado sob um iso3 fora desse conjunto era uma ficha completa e com fontes que nenhum visitante podia filtrar (a auditoria D-5 encontrou 50 de 1.384 assim: Barbados, Bahamas e um colunista radicado em Miami sob USA). Não foram apagados, foram escalonados por tier; este gate torna o escalonamento vinculante.
  6. check-citations — integridade do registro de citações.
  7. check-parameter-cache-citations — cada citation_id de parameter-cache/ resolve em citations.json, sem recurso a uma citação sintética de reserva. A API pública já estava limpa; este gate mantém limpo o cache a partir do qual ela é assada.
  8. check-scores — coerência dos escores compostos (ver O Índice Futuros).
  9. check-provenance — rejeita hashes ou digests fabricados (stubs de sha256 truncados) em qualquer parte de public/data; nasceu da auditoria de precisão de dados de julho de 2026.
  10. colombia/check-real-data — separa o real do sintético no corpus da Colômbia, para que nenhuma cifra de demonstração seja servida sem seu banner.

Estilo, taxonomia e cobertura:

  1. check-emdash-drift — a deriva de estilo (travessões) não reaparece no conteúdo já varrido.
  2. check-landing-stats — cada número impresso em public/landing.html continua sendo o número que os dados dizem. A landing é um artefato editado à mão, a página de maior tráfego e a única sem componente, dicionário ou teste por trás; suas estatísticas eram livres para apodrecer e apodreceram (a auditoria de 13 de agosto de 2026 a encontrou anunciando 6.726 citações contra um corpus de 6.855, e "300+ pilotos" contra um índice de 337). O gate recomputa cada cifra a partir de public/data/** do mesmo modo que a superfície correspondente, e imprime o literal a colar. Hoje verifica seis: fontes primárias distintas, países, eixos, citações, personas e instrumentos.
  3. check-catalog — o catálogo de datasets está completo e consistente.
  4. check-search-index (consultiva) — o índice semântico cobre o corpus.
  5. check-law-term-index — o índice de termos legais cobre 100% do corpus regulatório (instrument_count de regulation/_index.json). Índice local: Voyage segue opt-in e falha fechado.
  6. bake-trends-index --check — o índice de tendências assado coincide com seus insumos.
  7. check-vintage (consultiva) — quantas cifras publicadas quebram a regra de safra de dois anos. O limiar e a ordem de resolução do ano vêm de src/lib/vintage.ts, o mesmo módulo a partir do qual as tiras de manchete renderizam, então o gate mede o que o visitante vê. Existe porque bake-data-health pontua uma célula pela idade mediana de seus indicadores: a plataforma reportava 2 células rançosas de 260 enquanto 27% das cifras individuais quebravam a regra.
  8. check-adm1-coverage (consultiva) — o gate de honestidade da camada subnacional. "432 de 456 unidades ADM1 com dado real" era verdade apenas da sobreposição universal GDL; em nível de eixo nenhuma unidade levava mais de 5 dos 10 eixos medidos, cinco eixos estavam 100% modelados e 26 unidades tinham suas células escritas sob slugs que o app nunca pode pedir. Nada detectava isso, então derivou em silêncio por meses.
  9. check-i18n — a cobertura ES/EN/PT não retrocede.
  10. check-taxonomy — a cópia de interface não volta aos substantivos aposentados para as dez medidas setoriais (diez parámetros, ten dimensions, dez dimensões) nem aponta a navegação para /governance. Os dez são ejes / axes / eixos.
  11. check-hf-publish — sob public/data/hf-observations/ só pode viver nasa-power.json, e deve ser um armazém NASA POWER não vazio. Cascas vazias (observations: []) e feeds não publicados (CHIRPS, FIRMS, Ember…) são um defeito: servem um 200 que parece vivo.
  12. check-api — roda ao final, depois dos quatro bakes de prebuild (bake-data-health, bake-source-ledger, bake-api, bake-feeds), e verifica com sete grupos de asserções que a API pública estática espelha os dados atuais.

Os gates de proveniência em sentido estrito são o subconjunto check-traced, check-signals, check-source-links, check-citations, check-parameter-cache-citations e check-provenance — mas todos compartilham a mesma consequência: são a razão pela qual "toda cifra com sua fonte" não é um slogan, e sim uma invariante de compilação. Como uma citação se resolve em tela é explicado em Proveniência e citações.

Um padrão se repete nos gates mais novos (check-landing-stats, check-vintage, check-adm1-coverage, check-persona-scope): cada um nasceu de uma auditoria que encontrou uma afirmação verdadeira quando foi escrita e falsa quando foi lida, sem nada que a conectasse ao dado. O gate é essa conexão. É a mesma razão pela qual esta documentação existe e pela qual é auditada contra o código.

Cada número com sua fonte — a rastreabilidade é o contrato.