Skip to content

Procedência e citações ​

A procedência é o fosso competitivo do Futuros. Qualquer um pode exibir números; o raro é que cada número remonte a um registro de fonte primária específico em dois cliques, e que o sistema falhe na compilação se não conseguir. Este documento explica a maquinaria que sustenta esse contrato. Ele serve tanto ao Pilar 1 — Fundo Fiduciário de Dados (a procedência é o que torna o corpus verificável) quanto ao Pilar 2 — Modelo Soberano (os fatos vivem nas citações, nunca assados nos pesos).

O contrato de dois cliques ​

Todo número em tela carrega sua citação no próprio número: um sublinhado leve, não um chip [WB] depois da cifra. Um clique abre a referência (título, editor, ano); um segundo clique leva você ao registro exato da fonte, o país e o indicador precisos, não uma landing genérica. Essa especificidade é uma regra de build: o gate check-source-links faz o build falhar se um link aponta para uma fonte genérica em vez do registro país + indicador.

A exceção é explícita, não tácita: o próprio gate carrega uma lista LANDING_OK de fontes cujas citações resolvem legitimamente para uma página de landing ou de metodologia, camadas curadas e internas (gov-, event-, calc-, agg-, idx-), camadas documentais (EXA, GDELT, RSS, diários oficiais, sinais) e umas poucas fontes sem URL por registro (benchmarks da OCDE, IDEA, UNODC, V-Dem, FSI, INFORM, IMF PortWatch, FAOSTAT), cada uma com sua justificativa comentada no código. É um opt-out consciente e auditável, não um buraco: tudo o que não está nessa lista deve carregar deep link ou o build não compila. Se um dado não pode ser rastreado assim, não é publicado como valor.

Duas redes a mais fecham as brechas. GENERIC_URLS enumera páginas "genéricas de dataset" que diferem da raiz só por um path ou um fragmento (o dashboard do FSI, o formulário de busca vazio do USGS…): uma citação não isenta sobre uma delas é violação igual a uma landing nua. E os agregados regionais (-latam, -world, -subregion, -oecd no id) ficam isentos porque não existe um registro país+indicador para uma média supranacional. O desenho é fail-closed: uma fonte nova sem template de deep link cai para sua url_root e o build morre no prebuild; a mensagem de erro imprime as duas saídas válidas, escrever a url_template e rodar fix-source-deeplinks.ts, ou documentar a isenção em LANDING_OK com justificativa de uma linha.

Convenções de citation_id ​

O registro central de citações é public/data/citations.json: 22.291 registros com a forma {id, source, title, url, retrieved_at, year}. As células de dados e os componentes apontam para um registro por esse id (o campo que as células chamam de citation_id). O id carrega um prefixo que codifica sua origem: wb- (Banco Mundial, o mais numeroso), oecd-, who-, gov- (fonte governamental), event-, web- (busca web externa), etc.; corr- (correlação) existe como convenção reservada no código, hoje sem citações vivas. O prefixo não é cosmético: é o que permite resolver uma citação até sua fonte mesmo que a citação não esteja no registro em memória (ver reconstrução sintética).

A gramática do id é mecânica, e por isso parseável: <prefixo>-<geo>-<ano>, consumida da direita para a esquerda por parseMultiId(): se o último token são 4 dígitos, é o ano; se o seguinte é um ISO3 em minúsculas (ou latam, a média regional derivada pelo Futuros), é a geografia; tudo o que sobra é o prefixo. extractYear() aplica a mesma convenção com a regex -(\d{4})(?:[-_]|$). Exemplo real do registro: wb-si-pov-gini-arg-2024 → fonte WB, indicador SI.POV.GINI, Argentina, 2024.

Uma série fundida por ano pode resolver para dois registros. O citation_id do indicador é o do tip. Os anos que só a série anterior aporta levam seu citation_id no ponto, e ambos os ids ficam nas citações da célula. Na pobreza monetária do Peru (inei_per_pobreza), 2016-2025 resolvem ao informe gob.pe inei-per-pobreza-per-2025 e 2014-2015 ao cubo INEI inei-per-pobreza-per-2024.

O registro de fontes ​

src/lib/source-registry.ts mapeia mais de 200 SourceKey (acrônimos como WB, WHO, IMF) para seu SourceMeta: acrônimo, nome completo, agência, jurisdição, raiz de URL, template de URL, cadência, metodologia (com variantes _en / _pt), tier e licença. As peças-chave:

  • inferSourceKey() resolve um citation_id até sua fonte pelo prefixo.
  • deepLinkFor() constrói o link para o registro específico.
  • 43 funções url_template reconstroem o deep link por indicador e por país a partir do id sozinho: ou seja, dado um citation_id, o sistema pode regenerar a URL exata da fonte sem depender de que alguém a tenha salvado à mão.

É isso que garante o segundo clique do contrato: o deep link não é armazenado, é derivado, e por isso não pode ficar desatualizado em relação ao id. Dois detalhes do resolvedor importam na prática:

  • A ordem dos prefixos é semântica. inferSourceKey() é uma cadeia de regras startsWith em que vence a primeira coincidência, então os prefixos mais longos vêm antes (mepyd- deve preceder mep-; o comentário no código avisa isso). Os bancos centrais ilustram a disciplina de nomes: bcb- é o Brasil, bcbol- a Bolívia, bch- Honduras, bcch- o Chile, bccr- a Costa Rica, tokens únicos e terminados em hífen para que nenhum sombreie outro. Um prefixo não mapeado devolve UNKNOWN, que deliberadamente não está isento no gate de deep links: um prefixo novo sem registrar é uma regressão, não um caso permitido.
  • A licença viaja na fonte. SourceMeta.license (open, cc-by, non-commercial, share-alike, cite-only; ausente ⇒ aberta/permissiva) controla o bake: uma fonte cite-only pode ser citada sem redistribuir seus valores.

A safra viaja com a cifra: a regra de dois anos ​

A procedência responde de onde saiu este número. A regra de safra responde a outra metade: de quando ele é. Nenhuma observação publicada pode ter mais de dois anos, e o limiar vive uma única vez — STALE_AFTER_YEARS em src/lib/vintage.ts — para que o selo que o visitante vê e os gates que fazem o build falhar não possam discordar sobre o que significa "dois anos".

vintageOf() resolve o ano de uma célula em ordem decrescente de confiança: vintage_year explícito → year explícito → última observação da series → o ano codificado no citation_id. Um registro sem ano em nenhum desses lugares devolve null e não é renderizado como rançoso: "sem data" é um defeito distinto de "velho", e afirmar uma obsolescência que não podemos sustentar seria tão desonesto quanto escondê-la. VintageBadge imprime esse ano ao lado da cifra, em âmbar passada a regra, com o ano no tooltip.

No build a mesma regra é aplicada em duas escalas: check-vintage conta cifras individuais de parameter-cache/ sob uma catraca que só pode descer, e check-freshness mede a observação mais recente de cada família de datasets contra 730 dias. Ver os gates do prebuild.

Quando o próprio publicador não tem nada mais novo, a resposta nunca é inventar um número nem maquiar a data. Ou o dado é atualizado a partir de uma fonte corrente que cubra o mesmo conceito, ou a fronteira do publicador é declarada com sua evidência e uma data de reverificação que faz o build falhar ao vencer. Os oito indicadores mais velhos da Costa Rica — do Programa Estado de la Nación, entre 2015 e 2020 — são exatamente isso: a última observação que o PEN publica para o país, checada contra sua API em 9 de setembro de 2026, servida com o ano à vista.

Marcadores CiteRef ​

src/components/citations/CiteRef.tsx é a âncora de citação de toda a plataforma: o número ou o sintagma citado é o link, com um sublinhado leve. Não há chips [CEPAL] / [INEC] / [WB] depois da cifra. Ao passar o cursor (ou ao focar / tocar) mostra o mesmo popover de sempre (título, editor, ano, e a data de consulta: o retrieved_at da citação ou, na falta dele, o last_refreshed da fonte no ledger de frescor; se nenhum existe, nenhuma data é mostrada em vez de inventada); ao clicar leva à fonte externa. Se um trecho tem várias fontes, o popover as lista. A resolução segue esta ordem:

Resolução (letter F / procedência reproduzível): só conta um registro real do catálogo registrado em memória. Já não se fabrica um syntheticCitation(id) no render — um id sem registro se pinta como marcador solto («Referência não disponível nos dados atuais») em vez de inventar título/URL/retrieved_at. A reconstrução sintética continua existindo em src/lib/source-registry.ts para embeds e ferramentas que a invocam explicitamente, nunca como fallback silencioso do chip.

O chat registra as citações que o servidor emite durante o turno. Após um reload, chatStore rehidrata turns + citações desde sessionStorage, e ChatPanel volta a despejar essas citações no CitationRegistryProvider (o mesmo caminho de um link compartilhado #ask=). Sem esse passo, o corpus estava na sessão e o tooltip dizia «não disponível». Ao fechar um turno, o servidor também resgata contra citations.json qualquer [[cite:id]] citado que ainda não tivesse sido emitido como registro.

Letter F retirou o fallback sintético do render: um miss já não se maquia com título/URL inventados. As superfícies que sabem que suas citações foram registradas (página de indicador com strict) e o chat (após rehidratar a sessão) dependem do registro real; um id órfão sem fila em citations.json se declara como tal. Quando um id resolve para fonte mas não para URL externa útil, o clique abre a gaveta SourceTrace com a metodologia. Cada clique sobre um marcador emite o evento de telemetria source_click com {citação, fonte, ano}.

Ao redor do CiteRef vivem CitationChip (um invólucro que exige o texto a sublinhar), CitationDrawer e SourceTrace (uma gaveta de respaldo com metodologia e o log de falsificações). A marca de evidência externa não é um componente à parte: vive dentro do próprio CiteRef, que pinta o sublinhado em ouro tracejado quando o citation_id leva o prefixo web-. O percurso de ponta a ponta é: valor gerado no bake → citation_id → CiteRef → registro → deep link.

A gaveta SourceTrace carrega dois selos de data distintos, nunca intercambiáveis. extraído é o retrieved_at daquela citação concreta, julgado contra um relógio plano. fonte consultada é o last_refreshed da fonte no ledger de frescor (sources["series:<CHAVE>"] de refresh-meta.json), e é julgado contra a cadência da própria fonte, não contra esse relógio: o next_refresh_due do ledger, ou cadence_months quando é preciso derivá-lo. Um relógio plano era a régua errada para este selo nas duas direções: CEPALSTAT e Banxico publicam anualmente, então uma coleta de 100 dias atrás está exatamente em dia e chamá-la de stale inventava um alarme; IBGE, INEGI, DANE, INDEC e BCB publicam mensalmente, então essa mesma coleta perdeu três ciclos e o relógio plano não mostrava nada. O veredito é em dia enquanto não vence, toca atualizar dentro de um ciclo de carência e atualização vencida passado esse ciclo; se a linha não declara cadência (cadence_basis: "unavailable") mostram-se a data e a idade sem veredito, porque não há regra contra a qual julgá-la. Nenhum dos dois selos fala da idade do dado em si: isso é a regra de safra, com seu próprio limiar (STALE_AFTER_YEARS) e suas próprias comportas (check-vintage, check-freshness).

linkGradeFor(citationId, recordUrl) em src/lib/cite-link-grade.ts classifica o link que se pode oferecer sem vestir uma página de entrada como se fosse o registro:

  • record: o registro traz uma URL própria, não vazia e diferente de #.
  • deep: não há URL de registro, mas a url_template da fonte declara parâmetros (Function.length > 0) e deepLinkFor monta um link por id.
  • portal: o que dá para reconstruir é uma página de entrada (url_root, ou um template constante no estilo () => "https://…").

Em resolveCiteView (CiteRef.tsx) uma vista solta, sem registro de catálogo, fica no grau portal. drawerOnly também é verdadeiro quando o grau é portal: o clique abre a gaveta SourceTrace em vez de uma página inicial numa aba nova.

resolveTraceSource em SourceTrace.tsx monta a seção Fonte da gaveta. A URL é cite?.url || deepLinkFor(id): um url vazio cai no link reconstruído e não permanece como href="". Sem registro, a gaveta mostra a marca sem registro e a nota de que os metadados são reconstruídos a partir do identificador. Um link de grau portal leva a etiqueta de portal acima dele. O editor mostrado é o source do próprio registro quando a chave do registro de fontes é uma categoria genérica (CURATED, INTERNAL, UNKNOWN) ou CURATED_LAW; nos demais casos é o nome institucional da fonte.

Sinais: duas linhagens, uma honestidade ​

A camada de sinais (public/data/signals/) tem duas linhagens com contratos de procedência distintos, e a comporta check-signals distingue entre ambas pelo campo type:

  • Sinais extraídos por LLM (seis tipos: policy_announcement, funding, incident, milestone, risk, contradiction). Sua procedência é um documento do corpus: source_url deve existir no arquivo irmão documents/<eixo>__<ISO3>.json e toda figure deve ser substring verbatim do excerto desse documento. Quando o documento rota para fora da janela de 365 dias, o sinal é podado (com assento em state/pruned-signals.jsonl): um sinal nunca sobrevive ao seu documento.
  • Sinais earth_obs (determinísticos, sem LLM). São emitidos por bake-earthobs-signals.ts consultando fontes de observação da Terra legíveis por máquina: Climate TRACE v7 (emissões mensais por país; CC BY 4.0 segundo seus termos e o registro Zenodo do inventário, DOI conceitual 10.5281/zenodo.15014790) e INPE / TerraBrasilis (DETER mensal e PRODES anual; CC-BY-SA 4.0 segundo sua página de citações e licença). Aqui source_url é o GET exato que produziu o valor (o mês consultado viaja na própria URL) e não existe documento irmão, então a comporta lhes aplica um contrato estrutural: host https em lista branca (exatamente os que o gerador emite: api.climatetrace.org e terrabrasilis.dpi.inpe.br; Zenodo/DOI fica de fora de propósito, porque o Zenodo hospeda uploads arbitrários e admiti-lo permitiria citar um registro autopublicado), célula fixa (medio-ambiente-clima, com o arquivo coincidindo com parameter×geography_id), source_name dentro do conjunto conhecido, data ISO completa e figure verbatim dentro do seu próprio claim_es, com a mesma sequência de dígitos presente em claim_en/claim_pt (nenhuma língua pode afirmar outro número). A atribuição obrigatória de ambas as licenças é satisfeita em tela: source_name ("Climate TRACE", "INPE / DETER", "INPE / PRODES") é a âncora do link para a fonte.

O tipo earth_obs não pode nascer de um modelo: a lista VALID_TYPES do extrator LLM o exclui, de modo que um modelo que alucine esse tipo vê seu sinal descartado pelo validador determinístico. E as passadas de poda (a poda de órfãos do bake e prune-stale-signals) isentam earth_obs da regra do documento irmão (sem essa isenção, cada refresh do corpus documental os apagaria), mas os podam igualmente se deixarem de passar a comporta estrutural.

Essa isenção é limitada por um teto de idade por fonte (EARTH_OBS_MAX_AGE_DAYS), porque o documento irmão é o que faz um sinal LLM expirar e sem teto a via earth-obs seria a única que nunca expira: um upstream caído deixaria sua última leitura na tela para sempre. O teto é por fonte porque são as cadências das próprias fontes, não um número da casa: Climate TRACE e DETER são mensais (365 dias; o próprio gerador só busca 8 meses para trás por um mês publicado), enquanto no PRODES a date é o fim do período de referência (31 de julho) e a taxa consolidada é publicada por volta de novembro e vigora até chegar sua sucessora um ano depois, então um sinal PRODES vigente chega legitimamente a ~16 meses (teto de 550 dias). Um teto plano de ~400 dias teria apagado dado vivo e correto: em 8 de setembro de 2026 o sinal PRODES vigente tinha 404 dias. Adicionar uma fonte nova sem declarar sua cadência não compila: a tabela de tetos é indexada pela própria lista branca de source_name, então uma fonte sem teto é um erro de tipos, não uma herança silenciosa do valor padrão (365 dias, seguro apenas para um feed mensal e letal para um anual). O teto é aplicado em prune-stale-signals (com assento no ledger), não em check-signals: a comporta de build não depende do relógio, para que nenhum build fique vermelho porque o dado envelheceu sem que ninguém o tocasse. Na interface, a faixa climática de cada país mostra o sinal earth_obs mais recente como "último alerta", com sua data e sua fonte linkada.

Reconstrução sintética para embeds ​

As superfícies embutíveis (/embed/stat, /embed/chart, /embed/board) nem sempre carregam o registro completo de citações, pesaria demais. Para isso existe syntheticCitation(): reconstrói a citação a partir do citation_id e do registro de fontes (prefixo → SourceKey → url_template). Resultado: um card embutido num site de terceiros conserva sua citação e seu deep link, sem carregar os megabytes do registro. A procedência viaja com o dado, não com a página.

Três invariantes de honestidade na sintética: devolve null se o prefixo resolve para UNKNOWN (nunca inventa uma instituição); retrieved_at fica vazio (não fabrica uma data de recuperação que não ocorreu); e o ano só é incluído se for derivável do próprio id.

Badging de citações web externas ​

Nem todo fato vem do corpus gerado no bake. Quando o assistente usa busca web como último recurso (ver O assistente), essas citações carregam o prefixo web- e são marcadas visivelmente como externas: o sublinhado da cifra é ouro e tracejado, e o cartão de hover antepõe a etiqueta de dado não verificado. A razão é honestidade: um fato trazido da web aberta nunca deve se confundir com um dado do corpus verificado. O badging separa as duas classes de evidência na própria interface, para que o leitor saiba sempre quão firme é o piso sob cada número.

Log de falsificações ​

public/data/falsifications.json é o registro de afirmações que foram postas à prova, o reverso de "cada citação é uma fonte": cada falsificação é um compromisso auditável de que a plataforma corrige o que se mostra insustentável. SourceTrace o expõe junto à metodologia da fonte. É parte do contrato de honestidade detalhado em Metodologia e honestidade.


A procedência no Futuros é derivável, não prometida: o deep link é reconstruído do id, a citação sintética viaja até o embed, e o build se recusa a compilar se algum link fora do opt-out documentado (LANDING_OK) aponta para uma fonte genérica. Siga para O assistente para ver como o chat herda essa mesma disciplina, cada figura ancorada a um [[cite:id]]. E para a próxima fronteira (de procedência derivável a procedência criptograficamente verificável por terceiros), ver o roadmap técnico de otimização.

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