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,provideroumeta. Cada API externa tem um único adaptador emadapters.ts(WHO GHO, UNESCO UIS, OpenAlex, Climate Watch, UN SDG, UNHCR, OWID, IMF DataMapper, SDMX/DBnomics genérico…). As ondas posteriores vivem emscripts/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.LicensePosture—kind/redistribute/aggregates_only: o que pode ser republicado e o que só pode ser citado.- Verificação por sonda —
sources.tsé oSOURCE_REGISTRY_V2, com endpoints testados;regulation-sources.tsé o registro irmão para o rastreio de leis de IA. Osnso/*.jsonsã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 emcitations.json), sua cadência (cadence_days), um specverifyde re-verificação por amostragem (refetch-compare: pedir de novo uma amostra e comparar com o que foi salvo) e umexpected_volumedimensionado 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:
| Campo | Tipo / valores | Regra em validateRegistry() |
|---|---|---|
key | UPPER_SNAKE estável | regex ^[A-Z0-9_]+$; duplicado ou inválido = erro |
tier · priority · status | primary/secondary/curated · P0/P1/P2 · wired/ready/blocked/research | o tick ingest:due do daemon drena linhas ready |
engine | um dos 10 AdapterEngine | engine desconhecido = erro |
license | kind (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 |
auth | none/env_key/registration/approval + env | env = variável de ambiente que o adaptador lê |
endpoint | padrão de URL com placeholders {iso3}/{code} | ausente = erro |
deep_link | padrão de URL por-registro | é o que se estampa em citations.json |
cadence_days | número | o ledger de frescor compara contra isto |
geos · geo_level | ISO3[] ou "ALL" · country/adm1/adm2/point/supranational | geo fora do conjunto de 26 (25 países + LATAM) = warn |
pillars | slugs de dimensão | vazio com engine ≠ provider = warn |
expected_volume | número ≥ 0 | negativo ou não numérico = erro; dimensiona o sentinela de ±50% |
verify | sample_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):
runMultisource()→ células deparameter-cache/+ citações. É o grosso: o valor de cada indicador por país, com seucitation_id.runDocuments()→documents/enews/: documentos e notícias por dimensão e país.runSignals()→ sinais citados, extraídos por LLM, emsignals/.
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.ts — citations.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 debake-api.tsao fazer o bake da API pública).parameter-cache/(células indicador×país) eparameter-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:
| Campo | Exemplo (SI.POV.GINI) | O que responde |
|---|---|---|
source / source_url | World Bank Open Data | de onde saiu? |
cadence_months / next_refresh_due | 12 / 2027-03-02 | quando toca atualizar? |
last_refreshed | 2026-07-28 | quando pedimos pela última vez? |
last_observation_year | 2025 | quão novo é o dado em si? |
countries_with_data / total_countries | 22 / 25 | quanta cobertura? |
source_frontier_year / source_frontier_checked_at | 2025 / 2026-07-23 | existe algo mais novo no upstream? |
freshness_status / freshness_note | current | veredicto: 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 discoInvariante 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:
check-traced— toda cifra tem seu rastro até a fonte.check-signals— os sinais estão bem formados e citados.check-source-links— os links de citação apontam para o registro específico de país + indicador. A única exceção é a listaLANDING_OKdentro 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.check-pilots— coerência dos pilotos.check-persona-scope— nenhum 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 umiso3fora 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 sobUSA). Não foram apagados, foram escalonados por tier; este gate torna o escalonamento vinculante.check-citations— integridade do registro de citações.check-parameter-cache-citations— cadacitation_iddeparameter-cache/resolve emcitations.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.check-scores— coerência dos escores compostos (ver O Índice Futuros).check-provenance— rejeita hashes ou digests fabricados (stubs de sha256 truncados) em qualquer parte depublic/data; nasceu da auditoria de precisão de dados de julho de 2026.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:
check-emdash-drift— a deriva de estilo (travessões) não reaparece no conteúdo já varrido.check-landing-stats— cada número impresso empublic/landing.htmlcontinua 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 depublic/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.check-catalog— o catálogo de datasets está completo e consistente.check-search-index(consultiva) — o índice semântico cobre o corpus.check-law-term-index— o índice de termos legais cobre 100% do corpus regulatório (instrument_countderegulation/_index.json). Índice local: Voyage segue opt-in e falha fechado.bake-trends-index --check— o índice de tendências assado coincide com seus insumos.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 desrc/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 porquebake-data-healthpontua 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.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.check-i18n— a cobertura ES/EN/PT não retrocede.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.check-hf-publish— sobpublic/data/hf-observations/só pode vivernasa-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.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.