senado-br-mcp 3.18.3 → 3.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/scraper/ecidadania.js +21 -7
- package/dist/throttle/upstream.js +63 -2
- package/dist/tools/materias.js +1 -1
- package/dist/tools/orcamento-senado.js +2 -2
- package/dist/tools/senadores-admin.js +2 -2
- package/dist/tools/servidores.js +4 -4
- package/dist/tools/supridos.js +2 -2
- package/dist/utils/provenance.js +30 -10
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -553,18 +553,24 @@ Coverage spans all four upstream sources, each with its own `source`/`citation`/
|
|
|
553
553
|
- **Senado Federal — Execução Orçamentária e Financeira** — Arquimedes/Financeiro feed at `senado.gov.br`
|
|
554
554
|
- **Senado Federal — Portal e-Cidadania** — `www12.senado.leg.br/ecidadania`
|
|
555
555
|
|
|
556
|
-
Fields of the `concise` block (per response — one tool, one source; keys in this fixed order, `null` when the source does not expose the value):
|
|
556
|
+
Fields of the `concise` block (contract v1.3 since 3.18.5; per response — one tool, one source; keys in this fixed order; the first seven are always present, with `null` when the source does not expose the value; the optional ones are **absent**, never empty, when they do not apply):
|
|
557
557
|
|
|
558
558
|
| Field | Meaning |
|
|
559
559
|
|-------|---------|
|
|
560
560
|
| `source` | Official source name (e.g. *Senado Federal — Dados Abertos (Legislativo)*) |
|
|
561
561
|
| `source_url` | Canonical endpoint/item URL consulted (e.g. `…/processo/{id}`) |
|
|
562
562
|
| `data_vintage` | Vintage/competência of the data (e.g. `2024-03-15`, `2019`) — named `reference_period` before v3.5.0 |
|
|
563
|
-
| `retrieved_at` | ISO-8601 of the **upstream extraction** — carried through the cache, so it reflects when the data was actually fetched, not the build or the cache-hit time |
|
|
563
|
+
| `retrieved_at` | ISO-8601 of the **upstream extraction** — carried through the cache, so it reflects when the data was actually fetched, not the build or the cache-hit time; on merged responses, the oldest of the parts |
|
|
564
|
+
| `retrieval` | Measured diagnostics of the trip to the source (attempts, outcome), or `null` when nothing was fetched in the call (pure cache, local data) — since 3.11.0 (v1.1) |
|
|
564
565
|
| `citation` | Ready-to-use citation string (human-readable) |
|
|
565
566
|
| `license` | Source terms (Dados Abertos do Senado Federal) |
|
|
567
|
+
| `field_sources` | *Optional.* Per-field sub-sources, each with its own extraction instant — only on responses that merge reads (v1.2, since 3.17.0; see below) |
|
|
568
|
+
| `derived` / `derivation_note` | *Optional.* `true` + a fixed note when the server computed the values — the `estatisticas` modes (distribution, aggregates, rankings) of `senado_remuneracoes_servidores`, `senado_horas_extras`, `senado_ceaps`, `senado_execucao_orcamentaria` and `senado_suprimento_fundos`; the individual values they cite remain the Senate's own |
|
|
569
|
+
| `revision` | Whether the figure may still change at the source: `status` is `current` for every Senate source (the version in force at extraction; none is claimed `final`), and `note` says, for the administrative and budget sources, that the Senate may correct published data later |
|
|
566
570
|
|
|
567
|
-
|
|
571
|
+
No Senate source publishes notices alongside the data, so `notices` (also part of v1.3) does not appear. The text footer gains a line only for exceptions — here, the computed-values line on the `estatisticas` modes.
|
|
572
|
+
|
|
573
|
+
The canonical model behind the block also carries `dataset.id` (item/series identifier, e.g. `codigoMateria=137808`) and `api_version`; those are validated on every build and inform `attribution` (below), but are not part of the `concise` projection.
|
|
568
574
|
|
|
569
575
|
In addition to the `provenance` envelope, `structuredContent` carries a top-level **`attribution`** list — the distinct source URLs behind the response. This mirrors the naming proposed in [modelcontextprotocol#711](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/711) (where `attribution` is a list of source references at the response level), so the server stays forward-compatible if that RFC lands; the richer `provenance` object remains this server's own extension.
|
|
570
576
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { UpstreamError as PkgUpstreamError } from "@sbissoli/mcp-upstream";
|
|
13
13
|
import { UPSTREAM_TIMEOUT_MS } from "../types.js";
|
|
14
14
|
import { USER_AGENT } from "../version.js";
|
|
15
|
-
import { upstreamCall } from "../throttle/upstream.js";
|
|
15
|
+
import { CorpoForaDoPrazoError, PrazoDoCorpo, upstreamCall } from "../throttle/upstream.js";
|
|
16
16
|
export const ECIDADANIA_BASE = "https://www12.senado.leg.br/ecidadania";
|
|
17
17
|
/**
|
|
18
18
|
* Error carrying a `retryable` flag, mirroring UpstreamError so the tool layer
|
|
@@ -36,10 +36,9 @@ const isTransientStatus = (status) => status >= 500 || status === 429;
|
|
|
36
36
|
* (`X-Requested-With: XMLHttpRequest`, wider `Accept`).
|
|
37
37
|
*/
|
|
38
38
|
export async function fetchPage(path, opts = {}) {
|
|
39
|
-
const
|
|
39
|
+
const text = await idaEcidadania(path, opts.ajax
|
|
40
40
|
? { Accept: "text/html,*/*", "User-Agent": USER_AGENT, "X-Requested-With": "XMLHttpRequest" }
|
|
41
41
|
: { Accept: "text/html", "User-Agent": USER_AGENT }, "e-Cidadania");
|
|
42
|
-
const text = await resp.text();
|
|
43
42
|
// AJAX comment fragments are legitimately empty for events with zero comments — allowEmpty
|
|
44
43
|
// lets the caller treat "" as a valid (empty) result instead of a transient failure.
|
|
45
44
|
if (!opts.allowEmpty && !text.trim()) {
|
|
@@ -52,10 +51,10 @@ export async function fetchPage(path, opts = {}) {
|
|
|
52
51
|
* Ensures the response is valid JSON and an array.
|
|
53
52
|
*/
|
|
54
53
|
export async function fetchEcidadaniaJson(endpoint) {
|
|
55
|
-
const
|
|
54
|
+
const corpo = await idaEcidadania(endpoint, { Accept: "application/json", "User-Agent": USER_AGENT }, "e-Cidadania REST API");
|
|
56
55
|
let data;
|
|
57
56
|
try {
|
|
58
|
-
data =
|
|
57
|
+
data = JSON.parse(corpo);
|
|
59
58
|
}
|
|
60
59
|
catch {
|
|
61
60
|
throw ecidadaniaFetchError(`e-Cidadania REST API retornou JSON inválido para ${endpoint}`, true);
|
|
@@ -77,11 +76,18 @@ export async function fetchEcidadaniaJson(endpoint) {
|
|
|
77
76
|
* descartável e a política vale igual. O token bucket NÃO se aplica: é outro
|
|
78
77
|
* host, e o portal nunca passou por ele. As mensagens e o `retryable` são os
|
|
79
78
|
* de antes — `ecidadaniaError` (tools) e o pipeline os leem.
|
|
79
|
+
*
|
|
80
|
+
* Devolve o CORPO, lido aqui sob o que sobra dos 10 s (`PrazoDoCorpo`): no modo
|
|
81
|
+
* `response` o pacote só cobre até os cabeçalhos, e um portal que trava no
|
|
82
|
+
* corpo pendurava a chamada (e o cron) sem prazo.
|
|
80
83
|
*/
|
|
81
84
|
async function idaEcidadania(path, headers, rotulo) {
|
|
82
85
|
const url = `${ECIDADANIA_BASE}${path}`;
|
|
86
|
+
const prazo = new PrazoDoCorpo();
|
|
87
|
+
const foraDoPrazo = () => ecidadaniaFetchError(`${rotulo}: timeout (${UPSTREAM_TIMEOUT_MS / 1000}s) ao acessar ${path}`, true, "fonte");
|
|
88
|
+
let resp;
|
|
83
89
|
try {
|
|
84
|
-
|
|
90
|
+
resp = await upstreamCall().response(url, { headers, signal: prazo.signal });
|
|
85
91
|
}
|
|
86
92
|
catch (e) {
|
|
87
93
|
if (!(e instanceof PkgUpstreamError))
|
|
@@ -89,7 +95,7 @@ async function idaEcidadania(path, headers, rotulo) {
|
|
|
89
95
|
switch (e.kind) {
|
|
90
96
|
case "timeout":
|
|
91
97
|
case "aborted":
|
|
92
|
-
throw
|
|
98
|
+
throw foraDoPrazo();
|
|
93
99
|
case "network": {
|
|
94
100
|
const causa = e.cause instanceof Error ? e.cause.message : String(e.cause ?? "desconhecido");
|
|
95
101
|
throw ecidadaniaFetchError(`${rotulo}: falha de rede ao acessar ${path} (${causa})`, true, "fonte");
|
|
@@ -108,6 +114,14 @@ async function idaEcidadania(path, headers, rotulo) {
|
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
}
|
|
117
|
+
try {
|
|
118
|
+
return await prazo.texto(resp);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
if (e instanceof CorpoForaDoPrazoError)
|
|
122
|
+
throw foraDoPrazo();
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
111
125
|
}
|
|
112
126
|
/** Parse Brazilian number: 1.234.567 -> 1234567 */
|
|
113
127
|
export function parseBrNum(s) {
|
|
@@ -213,6 +213,52 @@ export function withUpstreamCall(fn) {
|
|
|
213
213
|
export function upstreamCall() {
|
|
214
214
|
return currentCall() ?? upstreamSenado.call();
|
|
215
215
|
}
|
|
216
|
+
/** O corpo não chegou inteiro dentro do orçamento da ida (ver `PrazoDoCorpo`). */
|
|
217
|
+
export class CorpoForaDoPrazoError extends Error {
|
|
218
|
+
constructor() {
|
|
219
|
+
super("corpo da resposta fora do prazo");
|
|
220
|
+
this.name = "CorpoForaDoPrazoError";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* O prazo da LEITURA DO CORPO de uma ida no modo `response`.
|
|
225
|
+
*
|
|
226
|
+
* No modo `response` o `@sbissoli/mcp-upstream` (0.4.x) desarma o cronômetro da
|
|
227
|
+
* tentativa quando chegam os CABEÇALHOS: o corpo, lido aqui depois, ficava sem
|
|
228
|
+
* prazo nenhum, e uma fonte que manda os cabeçalhos e trava no corpo pendurava a
|
|
229
|
+
* chamada até o Worker morrer. Medido em 13/10/2026 ao subir o nock do medical
|
|
230
|
+
* (o `delay()` do nock 15 passou a segurar o corpo, não os cabeçalhos).
|
|
231
|
+
*
|
|
232
|
+
* O `signal` entra na ida e o pacote o combina ao dele; quem o aborta é SÓ
|
|
233
|
+
* `texto()`, depois dos cabeçalhos — durante a ida ele nunca dispara, então a
|
|
234
|
+
* classificação do pacote (timeout × abortado, a contagem do `retrieval`) não
|
|
235
|
+
* muda. O prazo do corpo é o que sobra do orçamento TOTAL da ida, contado do
|
|
236
|
+
* início dela: cabeçalhos + corpo cabem nos mesmos 10 s, como sempre se disse.
|
|
237
|
+
*/
|
|
238
|
+
export class PrazoDoCorpo {
|
|
239
|
+
#controller = new AbortController();
|
|
240
|
+
#inicio = Date.now();
|
|
241
|
+
/** Para passar na ida (`call.response(url, { signal })`). */
|
|
242
|
+
get signal() {
|
|
243
|
+
return this.#controller.signal;
|
|
244
|
+
}
|
|
245
|
+
/** Lê o corpo como texto; estourando o orçamento, lança `CorpoForaDoPrazoError`. */
|
|
246
|
+
async texto(response) {
|
|
247
|
+
const resta = UPSTREAM_POLICY.budgetMs - (Date.now() - this.#inicio);
|
|
248
|
+
const timer = setTimeout(() => this.#controller.abort(), Math.max(0, resta));
|
|
249
|
+
try {
|
|
250
|
+
return await response.text();
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
if (this.#controller.signal.aborted)
|
|
254
|
+
throw new CorpoForaDoPrazoError();
|
|
255
|
+
throw e;
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
216
262
|
/**
|
|
217
263
|
* O `retrieval` medido nesta chamada de tool — `null` fora de um coletor ou
|
|
218
264
|
* quando a resposta veio inteira do cache (nenhuma ida). É o que
|
|
@@ -260,6 +306,20 @@ export async function upstreamFetch(path, params = {}, baseUrl, options = {}) {
|
|
|
260
306
|
for (let i = 1; i < attempts; i++)
|
|
261
307
|
incr("upstreamRetries");
|
|
262
308
|
};
|
|
309
|
+
const prazo = new PrazoDoCorpo();
|
|
310
|
+
const lerCorpo = async (r) => {
|
|
311
|
+
try {
|
|
312
|
+
return await prazo.texto(r);
|
|
313
|
+
}
|
|
314
|
+
catch (e) {
|
|
315
|
+
if (e instanceof CorpoForaDoPrazoError) {
|
|
316
|
+
incr("upstreamErrors");
|
|
317
|
+
logger.error("upstream_error", { path, status: 504, message: "corpo fora do prazo" });
|
|
318
|
+
throw new UpstreamError(`[${path}] Timeout na requisição upstream (${UPSTREAM_TIMEOUT_MS / 1000}s)`, 504, true, true);
|
|
319
|
+
}
|
|
320
|
+
throw e;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
263
323
|
let response;
|
|
264
324
|
inFlight++;
|
|
265
325
|
try {
|
|
@@ -269,6 +329,7 @@ export async function upstreamFetch(path, params = {}, baseUrl, options = {}) {
|
|
|
269
329
|
Accept: options.accept ?? "application/json",
|
|
270
330
|
"User-Agent": USER_AGENT,
|
|
271
331
|
},
|
|
332
|
+
signal: prazo.signal,
|
|
272
333
|
});
|
|
273
334
|
}
|
|
274
335
|
catch (err) {
|
|
@@ -278,7 +339,7 @@ export async function upstreamFetch(path, params = {}, baseUrl, options = {}) {
|
|
|
278
339
|
if (err.kind === "not_found" && options.on404 && err.response) {
|
|
279
340
|
incr("upstreamCalls");
|
|
280
341
|
log("upstream", path, 404, Date.now() - startTime, err.attempts - 1);
|
|
281
|
-
const corpo404 = await err.response
|
|
342
|
+
const corpo404 = await lerCorpo(err.response);
|
|
282
343
|
if (rotaInexistente(corpo404)) {
|
|
283
344
|
throw new UpstreamError(`[${path}] ${MSG_ROTA_INEXISTENTE} — o caminho montado não existe na API do Senado.`, 404, false, false, "defeito");
|
|
284
345
|
}
|
|
@@ -305,7 +366,7 @@ export async function upstreamFetch(path, params = {}, baseUrl, options = {}) {
|
|
|
305
366
|
if (contentLength && parseInt(contentLength, 10) > maxSize) {
|
|
306
367
|
throw new UpstreamError(`[${path}] Resposta upstream excede o limite de ${Math.round(maxSize / 1024 / 1024)} MB`, 413, false);
|
|
307
368
|
}
|
|
308
|
-
const text = await response
|
|
369
|
+
const text = await lerCorpo(response);
|
|
309
370
|
if (text.length > maxSize) {
|
|
310
371
|
throw new UpstreamError(`[${path}] Resposta upstream excede o limite de ${Math.round(maxSize / 1024 / 1024)} MB`, 413, false);
|
|
311
372
|
}
|
package/dist/tools/materias.js
CHANGED
|
@@ -134,7 +134,7 @@ export function parseDocumentoProcesso(d) {
|
|
|
134
134
|
* também entra como sub-fonte: o `retrieved_at` do topo é derivado pelo adaptador como o
|
|
135
135
|
* MAIS ANTIGO das três (contrato §3) e, sem este item, o instante do detalhe sumiria do
|
|
136
136
|
* bloco quando o resumo fosse mais velho. Antes da 3.17.0 o topo era o fetch do detalhe,
|
|
137
|
-
* feito DEPOIS das sub-fontes — com a v1.2
|
|
137
|
+
* feito DEPOIS das sub-fontes — com a regra da v1.2 (que a 1.3 mantém), a lib lançaria e a tool cairia.
|
|
138
138
|
*/
|
|
139
139
|
export function provenanceMateriaDetalhe(baseUrl, a) {
|
|
140
140
|
const base = baseUrl.replace(/\/$/, "");
|
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
14
14
|
import { upstreamFetch } from "../throttle/upstream.js";
|
|
15
15
|
import { errorFrom, ensureArray, safeInt, parseBRL } from "../utils/validation.js";
|
|
16
|
-
import { provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
16
|
+
import { derivadoDeEstatisticas, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
17
17
|
import { computarEstatisticas, arredondarEstatisticas, arredondarEntradas, } from "../utils/estatisticas.js";
|
|
18
18
|
import { CACHE_STATIC } from "../types.js";
|
|
19
19
|
const FINANCEIRO_BASE = "https://www.senado.gov.br";
|
|
@@ -249,7 +249,7 @@ export function registerOrcamentoSenadoTools(server) {
|
|
|
249
249
|
ano: params.ano ?? null,
|
|
250
250
|
totalLinhas: itens.length,
|
|
251
251
|
...estatisticasExecucao(itens, { tipo, campo: params.campo, agruparPor: params.agruparPor, topN: params.topN ?? 10 }),
|
|
252
|
-
}, prov);
|
|
252
|
+
}, derivadoDeEstatisticas(prov));
|
|
253
253
|
}
|
|
254
254
|
if (tipo === "despesas") {
|
|
255
255
|
let itens = ensureArray(bruto?.despesas).map(parseDespesa);
|
|
@@ -12,7 +12,7 @@ import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
|
12
12
|
import { admFetch, admFetchLarge } from "../throttle/adm.js";
|
|
13
13
|
import { errorFrom, ensureArray, normalizeText, parseBRL } from "../utils/validation.js";
|
|
14
14
|
import { unwrapAdmEnvelope } from "../utils/upstream-parse.js";
|
|
15
|
-
import { provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
15
|
+
import { derivadoDeEstatisticas, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
16
16
|
import { computarEstatisticas, arredondarEstatisticas, arredondarEntradas, } from "../utils/estatisticas.js";
|
|
17
17
|
import { CACHE_SEMI_STATIC, CACHE_STATIC } from "../types.js";
|
|
18
18
|
const norm = normalizeText;
|
|
@@ -157,7 +157,7 @@ export function registerSenadoresAdminTools(server, admBaseUrl) {
|
|
|
157
157
|
totalDespesas: filtrado.length,
|
|
158
158
|
valorTotal: totalGeral,
|
|
159
159
|
...estatisticasCeaps(filtrado, { agruparPor: params.agruparPor, topN: params.topN ?? 10 }),
|
|
160
|
-
}, prov);
|
|
160
|
+
}, derivadoDeEstatisticas(prov));
|
|
161
161
|
}
|
|
162
162
|
const limite = params.limite ?? 100;
|
|
163
163
|
const modo = params.modo ?? "por-senador";
|
package/dist/tools/servidores.js
CHANGED
|
@@ -12,7 +12,7 @@ import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
|
12
12
|
import { admFetch, admFetchLarge } from "../throttle/adm.js";
|
|
13
13
|
import { errorFrom, ensureArray, parseBRL } from "../utils/validation.js";
|
|
14
14
|
import { unwrapAdmEnvelope } from "../utils/upstream-parse.js";
|
|
15
|
-
import { provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
15
|
+
import { derivadoDeEstatisticas, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
16
16
|
import { computarEstatisticas, arredondarEstatisticas, arredondarEntradas, } from "../utils/estatisticas.js";
|
|
17
17
|
import { CACHE_SEMI_STATIC, CACHE_STATIC } from "../types.js";
|
|
18
18
|
import { matchesFiltro, matchesFiltroCampo } from "./contratacoes.js";
|
|
@@ -342,7 +342,7 @@ export function particionarPorUnidade(lista, indice, codUnidade) {
|
|
|
342
342
|
* organograma foi lido agora, o que também é. Com ele, o topo passa a ser o mais antigo
|
|
343
343
|
* das duas (derivado pelo adaptador, contrato §3), e a folha entra como sub-fonte própria
|
|
344
344
|
* para que o seu instante não suma do bloco. Antes da 3.17.0 o topo era o da folha, mais
|
|
345
|
-
* novo que o snapshot — com a v1.2
|
|
345
|
+
* novo que o snapshot — com a regra da v1.2 (que a 1.3 mantém), a lib lançaria e a tool cairia.
|
|
346
346
|
*
|
|
347
347
|
* `folhaDoCache` é o `fromCache` da leitura da folha; o snapshot embutido não é cache nem
|
|
348
348
|
* busca, então a sub-fonte dele não diz (`served_from_cache: null`).
|
|
@@ -473,7 +473,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
473
473
|
agruparPor: params.agruparPor,
|
|
474
474
|
topN: params.topN ?? 10,
|
|
475
475
|
}),
|
|
476
|
-
}, prov);
|
|
476
|
+
}, derivadoDeEstatisticas(prov));
|
|
477
477
|
}
|
|
478
478
|
if ((params.modo ?? "resumo") === "detalhe") {
|
|
479
479
|
const limite = params.limite ?? 50;
|
|
@@ -539,7 +539,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
539
539
|
total: itens.length,
|
|
540
540
|
valorTotal,
|
|
541
541
|
...estatisticasHorasExtras(itens, { agruparPor: params.agruparPor, topN: params.topN ?? 10 }),
|
|
542
|
-
}, prov);
|
|
542
|
+
}, derivadoDeEstatisticas(prov));
|
|
543
543
|
}
|
|
544
544
|
return resultWithProvenance({
|
|
545
545
|
ano: params.ano,
|
package/dist/tools/supridos.js
CHANGED
|
@@ -9,7 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
10
10
|
import { admFetch } from "../throttle/adm.js";
|
|
11
11
|
import { errorFrom, ensureArray } from "../utils/validation.js";
|
|
12
|
-
import { leituraDoCache, provenanceFor, resultWithProvenance, withFieldSources } from "../utils/provenance.js";
|
|
12
|
+
import { derivadoDeEstatisticas, leituraDoCache, provenanceFor, resultWithProvenance, withFieldSources } from "../utils/provenance.js";
|
|
13
13
|
import { computarEstatisticas, arredondarEstatisticas, arredondarEntradas, } from "../utils/estatisticas.js";
|
|
14
14
|
import { CACHE_STATIC } from "../types.js";
|
|
15
15
|
import { matchesFiltro } from "./contratacoes.js";
|
|
@@ -285,7 +285,7 @@ export function registerSupridosTools(server, admBaseUrl) {
|
|
|
285
285
|
modo: "estatisticas",
|
|
286
286
|
total: lista.length,
|
|
287
287
|
...estatisticasSuprimento(lista, { tipo, campo: params.campo, agruparPor: params.agruparPor, topN: params.topN ?? 10, nomePorCodigo: nomes?.nomes }),
|
|
288
|
-
}, nomes ? provenanceSuprimentoComNomes(prov, { fromCache }, params.ano, admBaseUrl, nomes, params.agruparPor) : prov);
|
|
288
|
+
}, derivadoDeEstatisticas(nomes ? provenanceSuprimentoComNomes(prov, { fromCache }, params.ano, admBaseUrl, nomes, params.agruparPor) : prov));
|
|
289
289
|
}
|
|
290
290
|
return resultWithProvenance({
|
|
291
291
|
ano: params.ano,
|
package/dist/utils/provenance.js
CHANGED
|
@@ -17,14 +17,16 @@
|
|
|
17
17
|
* - v1.1 (release 3.11.0): o concise tem 7 chaves fixas — `source`, `source_url`,
|
|
18
18
|
* `data_vintage` (ex-`reference_period`), `retrieved_at`, `citation`, `license` e
|
|
19
19
|
* `retrieval` (diagnóstico medido da ida à origem, ou `null`).
|
|
20
|
-
* - v1.2 (
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - v1.3 (
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* - v1.2 (release 3.17.0): acrescenta ao concise a chave opcional `field_sources`
|
|
21
|
+
* (sub-fontes por campo), só nas respostas que fundem recortes. Com ela a lib COBRA
|
|
22
|
+
* que o `retrieved_at` do topo seja o mais antigo entre as sub-fontes (lança
|
|
23
|
+
* `ProvenanceContractError`, o que derrubaria a tool) — por isso o adaptador o deriva
|
|
24
|
+
* por construção (`oldestRetrievedAt`), nunca a tool à mão.
|
|
25
|
+
* - v1.3 (EMITIDA desde a release 3.18.5; o `outputSchema` a aceitava desde a 3.17.0):
|
|
26
|
+
* o concise leva `revision` SEMPRE (por fonte, em `SOURCES`) e `derived` +
|
|
27
|
+
* `derivation_note` nos modos `estatisticas` (`derivadoDeEstatisticas`). `notices`
|
|
28
|
+
* não sai: nenhuma fonte do senado publica aviso junto com o dado. Sem cálculo, as
|
|
29
|
+
* chaves opcionais ficam AUSENTES, não vazias.
|
|
28
30
|
* - O rodapé de texto segue a redação fixada do contrato: linha de fonte
|
|
29
31
|
* ("Fonte: … · url · dados de … · extraído em …"), linha de licença e o aviso ao
|
|
30
32
|
* leitor de que a referência completa pode ser solicitada na própria conversa.
|
|
@@ -57,7 +59,7 @@ export const provenanceContext = createProvenanceContext({
|
|
|
57
59
|
locale: "pt-BR",
|
|
58
60
|
timezone: { offset: "-03:00", label: "horário de Brasília" },
|
|
59
61
|
defaultMode: "concise",
|
|
60
|
-
contractVersion: "1.
|
|
62
|
+
contractVersion: "1.3",
|
|
61
63
|
});
|
|
62
64
|
const BRASILIA = provenanceContext.timezone;
|
|
63
65
|
/**
|
|
@@ -134,7 +136,7 @@ const extraToCanonical = (extra = {}) => {
|
|
|
134
136
|
* `current` em todas as fontes do senado, `final` em nenhuma — só com prova da fonte).
|
|
135
137
|
* A `note` só repete o que o servidor JÁ diz nas instructions (`src/app-surface.ts`) sobre
|
|
136
138
|
* os dados administrativos; nas demais fontes não há texto publicado, então `null`.
|
|
137
|
-
*
|
|
139
|
+
* Sai no concise de toda resposta (v1.3).
|
|
138
140
|
*/
|
|
139
141
|
const REVISAO_VIGENTE = { status: "current", note: null };
|
|
140
142
|
const REVISAO_ADMINISTRATIVA = {
|
|
@@ -268,6 +270,24 @@ export function withFieldSources(prov, fieldSources) {
|
|
|
268
270
|
field_sources: fieldSources.map(toCanonicalFieldSource),
|
|
269
271
|
});
|
|
270
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Nota fixa dos modos `estatisticas` (mesma redação do ibge-br-mcp, adaptada à fonte):
|
|
275
|
+
* a distribuição, os agregados e o ranking são cálculo deste servidor; cada valor
|
|
276
|
+
* individual citado é o que o Senado publica.
|
|
277
|
+
*/
|
|
278
|
+
export const NOTA_DERIVACAO_ESTATISTICAS = "Estatísticas (distribuição, agregados e rankings) computadas pelo servidor a partir dos registros brutos retornados pela fonte; os valores individuais permanecem os originais do Senado Federal.";
|
|
279
|
+
/**
|
|
280
|
+
* Marca o bloco como derivado (contrato v1.3): `derived: true` + a nota fixa dos modos
|
|
281
|
+
* `estatisticas`. Aplique por último, sobre o bloco já completo (inclusive o de
|
|
282
|
+
* `withFieldSources`); o envelope é re-validado inteiro no contrato (build).
|
|
283
|
+
*/
|
|
284
|
+
export function derivadoDeEstatisticas(prov) {
|
|
285
|
+
return provenanceContext.build({
|
|
286
|
+
...prov,
|
|
287
|
+
derived: true,
|
|
288
|
+
derivation_note: NOTA_DERIVACAO_ESTATISTICAS,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
271
291
|
/** Rodapé de texto do contrato: fonte · url · vintage · extração, licença e aviso ao leitor. */
|
|
272
292
|
export const provenanceFooter = (p) => provenanceContext.footer(p);
|
|
273
293
|
/**
|
package/dist/version.js
CHANGED