senado-br-mcp 3.18.4 → 3.19.0
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/tools/comissoes.js +8 -2
- package/dist/tools/ecidadania.js +22 -10
- package/dist/tools/materias.js +1 -1
- package/dist/tools/orcamento-senado.js +6 -4
- package/dist/tools/referencia.js +13 -4
- package/dist/tools/senadores-admin.js +5 -3
- package/dist/tools/servidores.js +19 -9
- package/dist/tools/supridos.js +2 -2
- package/dist/tools/votacao-comissao.js +32 -7
- package/dist/tools/votacoes.js +13 -7
- package/dist/utils/provenance.js +62 -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 pt-BR note naming the calculation whenever the server computed values instead of copying them (since 3.19.0, the whole class): the `estatisticas` modes (distribution, aggregates, rankings); group sums, counts and averages — CEAPS `valorTotal` and its per-senator/type/month/supplier modes, the budget execution aggregates (`por-ano`, the default, included), the payroll `resumo`, the overtime `valorTotal`, the `relatoria` distribution, seated senators per party/UF, the historical e-Cidadania national totals; the payroll gross (`bruto`, sum of seven components); servants assigned to an org subtree (`subordinadasA`); a vote tally counted from nominal votes when the source publishes none (plenary open roll calls, committee votes without totals); e-Cidadania percentages, consensus/polarisation rankings and the estimated idea creation date; the current legislature's period. Absent when every number is the source's own (raw lists, `detalhe` of the budget, published tallies). The individual values cited remain the Senate's |
|
|
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
|
|
package/dist/tools/comissoes.js
CHANGED
|
@@ -17,7 +17,7 @@ import { upstreamFetch, UpstreamError } from "../throttle/upstream.js";
|
|
|
17
17
|
import { toolError, errorFrom, ensureArray, safeInt, toBool, normalizeText } from "../utils/validation.js";
|
|
18
18
|
import { digArrayRoot } from "../utils/upstream-parse.js";
|
|
19
19
|
import { escolherReuniao, ehReuniaoInexistente, mensagemCodigoInexistente } from "./resolver-reuniao.js";
|
|
20
|
-
import { leituraDoCache, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
20
|
+
import { NOTA_DERIVACAO_SOMAS, derivadoSe, leituraDoCache, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
21
21
|
import { CACHE_SEMI_STATIC, CACHE_DYNAMIC, CACHE_ON_DEMAND } from "../types.js";
|
|
22
22
|
export function formatDateYMD(d) {
|
|
23
23
|
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
|
|
@@ -154,6 +154,12 @@ export function provenanceReunioesComissao(baseUrl, sigla, di, df, partes) {
|
|
|
154
154
|
: {}),
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* `senado_distribuicao_materias` (contract v1.3): in `relatoria` the source sends one row per
|
|
159
|
+
* (parlamentar, unidade) and the server sums `Quantidade` per parlamentar — derived; in `autoria`
|
|
160
|
+
* each count is the source's own (ordering only).
|
|
161
|
+
*/
|
|
162
|
+
export const provenanceDaDistribuicao = (prov, tipo) => derivadoSe(prov, tipo === "relatoria", NOTA_DERIVACAO_SOMAS);
|
|
157
163
|
export function registerComissoesTools(server, baseUrl) {
|
|
158
164
|
// E1. senado_listar_comissoes
|
|
159
165
|
server.tool("senado_listar_comissoes", "Lista comissões (colegiados) ativas do Senado, com filtros por `tipo` (permanente, temporaria, cpi, mista) e `ativa`. Retorna `{ count, comissoes }`, cada item com `codigo`, `sigla`, `nome`, `tipo`, `casa` e `ativa`. O endpoint só traz comissões ativas, logo `ativa=false` resulta em lista vazia. Use para descobrir a `sigla` exigida por `senado_obter_comissao` e `senado_reunioes_comissao`.", {
|
|
@@ -554,7 +560,7 @@ export function registerComissoesTools(server, baseUrl) {
|
|
|
554
560
|
tipo,
|
|
555
561
|
count: parlamentares.length,
|
|
556
562
|
parlamentares,
|
|
557
|
-
}, prov);
|
|
563
|
+
}, provenanceDaDistribuicao(prov, tipo));
|
|
558
564
|
}
|
|
559
565
|
catch (e) {
|
|
560
566
|
return errorFrom(e, "Erro ao obter distribuição de matérias");
|
package/dist/tools/ecidadania.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
14
14
|
import { errorFrom } from "../utils/validation.js";
|
|
15
|
-
import { leituraDoCache, provenanceEcidadania, provenanceArquimedesVotos, resultWithProvenance, withFieldSources, } from "../utils/provenance.js";
|
|
15
|
+
import { leituraDoCache, provenanceEcidadania, NOTA_DERIVACAO_DATA_ESTIMADA, NOTA_DERIVACAO_PERCENTUAIS, NOTA_DERIVACAO_RANKING_ECIDADANIA, NOTA_DERIVACAO_SOMAS, derivado, derivadoSe, provenanceArquimedesVotos, resultWithProvenance, withFieldSources, } from "../utils/provenance.js";
|
|
16
16
|
import { tagUntrustedFields, tagUntrustedList, neutralizeUntrustedText, sanitizeScrapedText } from "../utils/untrusted.js";
|
|
17
17
|
import { CACHE_ON_DEMAND } from "../types.js";
|
|
18
18
|
import { resolveList, writeDetalheThrough } from "../scraper/store.js";
|
|
@@ -90,6 +90,18 @@ export function provenanceEventoDetalhe(item, id, leitura, corpusMeta) {
|
|
|
90
90
|
subFonteDoCorpus("/principalaudiencia", "eventos", corpusMeta, ["comentarios"]),
|
|
91
91
|
]);
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Contract v1.3 `derived` on e-Cidadania responses. Every consulta carries percentages the server
|
|
95
|
+
* computes (`buildConsultaResumo`), and `totalVotos` too when the source sends no total; the
|
|
96
|
+
* analysis and the poll suggestion add their own polarisation/consensus metrics and ranking on top;
|
|
97
|
+
* an idea's creation date is an estimate; the historical archive's national totals are summed from
|
|
98
|
+
* the per-UF rows of the CSV.
|
|
99
|
+
*/
|
|
100
|
+
export const NOTA_DERIVACAO_ANALISE_CONSULTAS = `${NOTA_DERIVACAO_RANKING_ECIDADANIA} ${NOTA_DERIVACAO_PERCENTUAIS}`;
|
|
101
|
+
export const provenanceDasConsultas = (prov) => derivado(prov, NOTA_DERIVACAO_PERCENTUAIS);
|
|
102
|
+
export const provenanceDaAnaliseDeConsultas = (prov) => derivado(prov, NOTA_DERIVACAO_ANALISE_CONSULTAS);
|
|
103
|
+
export const provenanceDasIdeias = (prov, ideias) => derivadoSe(prov, ideias.some((i) => i.dataCriacaoEstimada != null), NOTA_DERIVACAO_DATA_ESTIMADA);
|
|
104
|
+
export const provenanceDosVotosHistoricos = (prov) => derivado(prov, NOTA_DERIVACAO_SOMAS);
|
|
93
105
|
/**
|
|
94
106
|
* Proveniência de `senado_ecidadania_sugerir_tema_enquete`: as sugestões cruzam DOIS corpora
|
|
95
107
|
* do D1 (consultas e ideias), cada um com o seu `lastScrapedAt`. Até a 3.18.1 o topo levava só
|
|
@@ -103,10 +115,10 @@ export function provenanceSugestaoEnquete(consultasMeta, ideiasMeta) {
|
|
|
103
115
|
dataset_id: "consultas+ideias",
|
|
104
116
|
retrieved_at: typeof ts === "string" && ts ? ts : undefined,
|
|
105
117
|
});
|
|
106
|
-
return withFieldSources(prov, [
|
|
118
|
+
return derivado(withFieldSources(prov, [
|
|
107
119
|
subFonteDoCorpus("/principalmateria", "consultas", consultasMeta, campos),
|
|
108
120
|
subFonteDoCorpus("/principalideia", "ideias", ideiasMeta, campos),
|
|
109
|
-
]);
|
|
121
|
+
]), NOTA_DERIVACAO_ANALISE_CONSULTAS);
|
|
110
122
|
}
|
|
111
123
|
// ══════════════════════════════════════════════════════════════════════════
|
|
112
124
|
// Tool registration
|
|
@@ -158,7 +170,7 @@ export function registerECidadaniaTools(server, _baseUrl, env, ctx) {
|
|
|
158
170
|
const limite = params.limite ?? 20;
|
|
159
171
|
const offset = ((params.pagina ?? 1) - 1) * limite;
|
|
160
172
|
const out = filtered.slice(offset, offset + limite);
|
|
161
|
-
return resultWithProvenance({ count: out.length, consultas: tagUntrustedList("consultas", out), meta }, provLista("/principalmateria", "consultas", meta));
|
|
173
|
+
return resultWithProvenance({ count: out.length, consultas: tagUntrustedList("consultas", out), meta }, provenanceDasConsultas(provLista("/principalmateria", "consultas", meta)));
|
|
162
174
|
}
|
|
163
175
|
catch (e) {
|
|
164
176
|
return ecidadaniaError(e);
|
|
@@ -170,7 +182,7 @@ export function registerECidadaniaTools(server, _baseUrl, env, ctx) {
|
|
|
170
182
|
const { value: r, fetchedAt } = await cachedFetchWithMeta("ecidadania_consulta", { id: params.id }, CACHE_ON_DEMAND, () => obterConsultaInternal(params.id));
|
|
171
183
|
writeDetalheThrough(db, ctx, "consultas", params.id, r);
|
|
172
184
|
const detalhe = buildConsultaDetalheResult(r);
|
|
173
|
-
return resultWithProvenance(tagUntrustedFields("consultas", detalhe), provDetalhe(r, `/visualizacaomateria?id=${params.id}`, `consulta=${params.id}`, fetchedAt));
|
|
185
|
+
return resultWithProvenance(tagUntrustedFields("consultas", detalhe), provenanceDasConsultas(provDetalhe(r, `/visualizacaomateria?id=${params.id}`, `consulta=${params.id}`, fetchedAt)));
|
|
174
186
|
}
|
|
175
187
|
catch (e) {
|
|
176
188
|
return ecidadaniaError(e);
|
|
@@ -216,7 +228,7 @@ export function registerECidadaniaTools(server, _baseUrl, env, ctx) {
|
|
|
216
228
|
.slice(0, limite);
|
|
217
229
|
criterio = `≥${percentualMinimo}% numa direção, mínimo ${minimoVotos} votos`;
|
|
218
230
|
}
|
|
219
|
-
return resultWithProvenance({ modo, criterio, count: filtered.length, consultas: tagUntrustedList("consultas", filtered), meta }, provLista("/principalmateria", "consultas", meta));
|
|
231
|
+
return resultWithProvenance({ modo, criterio, count: filtered.length, consultas: tagUntrustedList("consultas", filtered), meta }, provenanceDaAnaliseDeConsultas(provLista("/principalmateria", "consultas", meta)));
|
|
220
232
|
}
|
|
221
233
|
catch (e) {
|
|
222
234
|
return ecidadaniaError(e);
|
|
@@ -248,7 +260,7 @@ export function registerECidadaniaTools(server, _baseUrl, env, ctx) {
|
|
|
248
260
|
ideias: tagUntrustedList("ideias", ideias),
|
|
249
261
|
avisoDatas: AVISO_DATAS_IDEIA,
|
|
250
262
|
meta,
|
|
251
|
-
}, provLista("/principalideia", "ideias", meta));
|
|
263
|
+
}, provenanceDasIdeias(provLista("/principalideia", "ideias", meta), ideias));
|
|
252
264
|
}
|
|
253
265
|
catch (e) {
|
|
254
266
|
return ecidadaniaError(e);
|
|
@@ -269,7 +281,7 @@ export function registerECidadaniaTools(server, _baseUrl, env, ctx) {
|
|
|
269
281
|
comentarios: null,
|
|
270
282
|
avisoDatas: AVISO_DATAS_IDEIA,
|
|
271
283
|
};
|
|
272
|
-
return resultWithProvenance(tagUntrustedFields("ideias", detalhe), provDetalhe(r, `/visualizacaoideia?id=${params.id}`, `ideia=${params.id}`, fetchedAt));
|
|
284
|
+
return resultWithProvenance(tagUntrustedFields("ideias", detalhe), provenanceDasIdeias(provDetalhe(r, `/visualizacaoideia?id=${params.id}`, `ideia=${params.id}`, fetchedAt), [detalhe]));
|
|
273
285
|
}
|
|
274
286
|
catch (e) {
|
|
275
287
|
return ecidadaniaError(e);
|
|
@@ -474,10 +486,10 @@ export function registerECidadaniaTools(server, _baseUrl, env, ctx) {
|
|
|
474
486
|
if (out.length === 0 && (meta.fonte === "ao-vivo" || meta.motivo === "d1-vazio" || meta.motivo === "d1-indisponivel")) {
|
|
475
487
|
payload.aviso = "Acervo de votos ainda não ingerido (primeira carga semanal pendente) ou filtro sem correspondência.";
|
|
476
488
|
}
|
|
477
|
-
return resultWithProvenance(payload, provenanceArquimedesVotos({
|
|
489
|
+
return resultWithProvenance(payload, provenanceDosVotosHistoricos(provenanceArquimedesVotos({
|
|
478
490
|
reference_period: referencePeriod ?? undefined,
|
|
479
491
|
retrieved_at: typeof meta.lastScrapedAt === "string" && meta.lastScrapedAt ? meta.lastScrapedAt : undefined,
|
|
480
|
-
}));
|
|
492
|
+
})));
|
|
481
493
|
}
|
|
482
494
|
catch (e) {
|
|
483
495
|
return ecidadaniaError(e);
|
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 { NOTA_DERIVACAO_SOMAS, derivadoDeEstatisticas, derivadoSe, 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";
|
|
@@ -80,6 +80,8 @@ export function parseReceita(r) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
/** Aggregate normalized despesas by a key, summing the five value columns. */
|
|
83
|
+
/** Todo modo fora de `detalhe` soma por grupo (por-ano, o padrão, também): é cálculo do servidor. */
|
|
84
|
+
export const provenanceDaExecucao = (prov, modo) => derivadoSe(prov, modo !== "detalhe", NOTA_DERIVACAO_SOMAS);
|
|
83
85
|
export function agregarDespesas(itens, chave) {
|
|
84
86
|
const grupos = new Map();
|
|
85
87
|
for (const d of itens) {
|
|
@@ -249,7 +251,7 @@ export function registerOrcamentoSenadoTools(server) {
|
|
|
249
251
|
ano: params.ano ?? null,
|
|
250
252
|
totalLinhas: itens.length,
|
|
251
253
|
...estatisticasExecucao(itens, { tipo, campo: params.campo, agruparPor: params.agruparPor, topN: params.topN ?? 10 }),
|
|
252
|
-
}, prov);
|
|
254
|
+
}, derivadoDeEstatisticas(prov));
|
|
253
255
|
}
|
|
254
256
|
if (tipo === "despesas") {
|
|
255
257
|
let itens = ensureArray(bruto?.despesas).map(parseDespesa);
|
|
@@ -272,7 +274,7 @@ export function registerOrcamentoSenadoTools(server) {
|
|
|
272
274
|
agregado = agregado.sort((a, b) => String(a.chave).localeCompare(String(b.chave)));
|
|
273
275
|
resultado = { agregado: agregado.slice(0, limite) };
|
|
274
276
|
}
|
|
275
|
-
return resultWithProvenance({ tipo, modo, ano: params.ano ?? null, totalLinhas: itens.length, ...resultado }, prov);
|
|
277
|
+
return resultWithProvenance({ tipo, modo, ano: params.ano ?? null, totalLinhas: itens.length, ...resultado }, provenanceDaExecucao(prov, modo));
|
|
276
278
|
}
|
|
277
279
|
// receitas
|
|
278
280
|
let itens = ensureArray(bruto?.receitas).map(parseReceita);
|
|
@@ -303,7 +305,7 @@ export function registerOrcamentoSenadoTools(server) {
|
|
|
303
305
|
: agregado.sort((a, b) => a.chave.localeCompare(b.chave));
|
|
304
306
|
resultado = { agregado: agregado.slice(0, limite) };
|
|
305
307
|
}
|
|
306
|
-
return resultWithProvenance({ tipo, modo, ano: params.ano ?? null, totalLinhas: itens.length, ...resultado }, prov);
|
|
308
|
+
return resultWithProvenance({ tipo, modo, ano: params.ano ?? null, totalLinhas: itens.length, ...resultado }, provenanceDaExecucao(prov, modo));
|
|
307
309
|
}
|
|
308
310
|
catch (e) {
|
|
309
311
|
return errorFrom(e, "Erro ao consultar execução orçamentária");
|
package/dist/tools/referencia.js
CHANGED
|
@@ -9,7 +9,7 @@ import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
|
9
9
|
import { upstreamFetch } from "../throttle/upstream.js";
|
|
10
10
|
import { errorFrom, ensureArray } from "../utils/validation.js";
|
|
11
11
|
import { digArrayRoot } from "../utils/upstream-parse.js";
|
|
12
|
-
import { provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
12
|
+
import { NOTA_DERIVACAO_LEGISLATURA, NOTA_DERIVACAO_LEGISLATURA_PADRAO, NOTA_DERIVACAO_SOMAS, derivado, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
13
13
|
import { CACHE_STATIC } from "../types.js";
|
|
14
14
|
export const TIPOS_MATERIA = [
|
|
15
15
|
{ sigla: "PEC", nome: "Proposta de Emenda à Constituição", descricao: "Altera a Constituição Federal" },
|
|
@@ -64,6 +64,15 @@ export function deriveLegislaturaAtual(parlamentares) {
|
|
|
64
64
|
// Fallback to the 57th legislatura (2023-2027)
|
|
65
65
|
return { numero: 57, periodo: "2023-2027", dataInicio: "2023-02-01", dataFim: "2027-01-31" };
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* `tabela=legislatura-atual` (contract v1.3): period and dates are always the server's arithmetic;
|
|
69
|
+
* when the source sends no legislatura number the number itself is the server's fallback, and the
|
|
70
|
+
* note says so.
|
|
71
|
+
*/
|
|
72
|
+
export function provenanceDaLegislatura(prov, parlamentares) {
|
|
73
|
+
const doFonte = Boolean(parlamentares[0]?.Mandato?.PrimeiraLegislaturaDoMandato?.NumeroLegislatura);
|
|
74
|
+
return derivado(prov, doFonte ? NOTA_DERIVACAO_LEGISLATURA : NOTA_DERIVACAO_LEGISLATURA_PADRAO);
|
|
75
|
+
}
|
|
67
76
|
/** Tally seated senators per party (sigla → count). */
|
|
68
77
|
export function tabularPartidos(parlamentares) {
|
|
69
78
|
const counts = {};
|
|
@@ -125,7 +134,7 @@ export function registerReferenciaTools(server, baseUrl) {
|
|
|
125
134
|
const prov = provenanceFor("SENADO_LEGIS", baseUrl, "/senador/lista/atual", {
|
|
126
135
|
dataset_id: "tabela=partidos", retrieved_at: fetchedAt,
|
|
127
136
|
});
|
|
128
|
-
return resultWithProvenance({ tabela: params.tabela, count: partidos.length, totalSenadores: parlamentares.length, partidos }, prov);
|
|
137
|
+
return resultWithProvenance({ tabela: params.tabela, count: partidos.length, totalSenadores: parlamentares.length, partidos }, derivado(prov, NOTA_DERIVACAO_SOMAS));
|
|
129
138
|
}
|
|
130
139
|
case "ufs": {
|
|
131
140
|
const { value, fetchedAt } = await fetchSenadoresAtuais(baseUrl);
|
|
@@ -134,7 +143,7 @@ export function registerReferenciaTools(server, baseUrl) {
|
|
|
134
143
|
const prov = provenanceFor("SENADO_LEGIS", baseUrl, "/senador/lista/atual", {
|
|
135
144
|
dataset_id: "tabela=ufs", retrieved_at: fetchedAt,
|
|
136
145
|
});
|
|
137
|
-
return resultWithProvenance({ tabela: params.tabela, count: ufs.length, totalSenadores: parlamentares.length, ufs }, prov);
|
|
146
|
+
return resultWithProvenance({ tabela: params.tabela, count: ufs.length, totalSenadores: parlamentares.length, ufs }, derivado(prov, NOTA_DERIVACAO_SOMAS));
|
|
138
147
|
}
|
|
139
148
|
case "legislatura-atual": {
|
|
140
149
|
const { value, fetchedAt } = await fetchSenadoresAtuais(baseUrl);
|
|
@@ -142,7 +151,7 @@ export function registerReferenciaTools(server, baseUrl) {
|
|
|
142
151
|
const prov = provenanceFor("SENADO_LEGIS", baseUrl, "/senador/lista/atual", {
|
|
143
152
|
dataset_id: "tabela=legislatura-atual", retrieved_at: fetchedAt,
|
|
144
153
|
});
|
|
145
|
-
return resultWithProvenance({ tabela: params.tabela, ...deriveLegislaturaAtual(parlamentares) }, prov);
|
|
154
|
+
return resultWithProvenance({ tabela: params.tabela, ...deriveLegislaturaAtual(parlamentares) }, provenanceDaLegislatura(prov, parlamentares));
|
|
146
155
|
}
|
|
147
156
|
case "tipos-norma": {
|
|
148
157
|
const { value: response, fetchedAt } = await cachedFetchWithMeta("senado_tipos_norma", {}, CACHE_STATIC, () => upstreamFetch("/legislacao/tiposNorma", {}, baseUrl));
|
|
@@ -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 { NOTA_DERIVACAO_SOMAS, derivado, 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;
|
|
@@ -40,6 +40,8 @@ export function filtrarCeaps(itens, f) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
/** Aggregate CEAPS items by a key extractor. */
|
|
43
|
+
/** Toda resposta fora de `estatisticas` leva `valorTotal` (soma do servidor), em qualquer modo. */
|
|
44
|
+
export const provenanceDoCeaps = (prov) => derivado(prov, NOTA_DERIVACAO_SOMAS);
|
|
43
45
|
export function agregarCeaps(itens, chave, extra) {
|
|
44
46
|
const grupos = new Map();
|
|
45
47
|
for (const d of itens) {
|
|
@@ -157,7 +159,7 @@ export function registerSenadoresAdminTools(server, admBaseUrl) {
|
|
|
157
159
|
totalDespesas: filtrado.length,
|
|
158
160
|
valorTotal: totalGeral,
|
|
159
161
|
...estatisticasCeaps(filtrado, { agruparPor: params.agruparPor, topN: params.topN ?? 10 }),
|
|
160
|
-
}, prov);
|
|
162
|
+
}, derivadoDeEstatisticas(prov));
|
|
161
163
|
}
|
|
162
164
|
const limite = params.limite ?? 100;
|
|
163
165
|
const modo = params.modo ?? "por-senador";
|
|
@@ -186,7 +188,7 @@ export function registerSenadoresAdminTools(server, admBaseUrl) {
|
|
|
186
188
|
totalDespesas: filtrado.length,
|
|
187
189
|
valorTotal: totalGeral,
|
|
188
190
|
...resultado,
|
|
189
|
-
}, prov);
|
|
191
|
+
}, provenanceDoCeaps(prov));
|
|
190
192
|
}
|
|
191
193
|
catch (e) {
|
|
192
194
|
return errorFrom(e, "Erro ao consultar despesas CEAPS");
|
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 { NOTA_DERIVACAO_BRUTO, NOTA_DERIVACAO_SOMAS, NOTA_DERIVACAO_SUBORDINADAS, derivado, 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";
|
|
@@ -55,6 +55,16 @@ export function resumoRemuneracao(r) {
|
|
|
55
55
|
num(r.gratificacao_natalina) + num(r.horas_extras) + num(r.outras_eventuais) + num(r.abono_permanencia)) * 100) / 100,
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Provenance of the non-statistics remuneration modes (contract v1.3): `detalhe` carries the
|
|
60
|
+
* server-summed `bruto` per row; `resumo` (the default) sums and averages that `bruto` per
|
|
61
|
+
* `tipoFolha`, so its note names both calculations.
|
|
62
|
+
*/
|
|
63
|
+
export const provenanceDaRemuneracao = (prov, modo) => modo === "detalhe"
|
|
64
|
+
? derivado(prov, NOTA_DERIVACAO_BRUTO)
|
|
65
|
+
: derivado(prov, `${NOTA_DERIVACAO_SOMAS} ${NOTA_DERIVACAO_BRUTO}`);
|
|
66
|
+
/** Overtime outside `estatisticas`: `valorTotal` at the top is the server's sum of the rows. */
|
|
67
|
+
export const provenanceDasHorasExtras = (prov) => derivado(prov, NOTA_DERIVACAO_SOMAS);
|
|
58
68
|
/** The 7 columns that sum into `bruto` (identical composition to `resumoRemuneracao`). */
|
|
59
69
|
const COMPONENTES_BRUTO = [
|
|
60
70
|
"remuneracaoBasica", "vantagensPessoais", "funcaoComissionada", "gratificacaoNatalina",
|
|
@@ -342,13 +352,13 @@ export function particionarPorUnidade(lista, indice, codUnidade) {
|
|
|
342
352
|
* organograma foi lido agora, o que também é. Com ele, o topo passa a ser o mais antigo
|
|
343
353
|
* das duas (derivado pelo adaptador, contrato §3), e a folha entra como sub-fonte própria
|
|
344
354
|
* 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
|
|
355
|
+
* 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
356
|
*
|
|
347
357
|
* `folhaDoCache` é o `fromCache` da leitura da folha; o snapshot embutido não é cache nem
|
|
348
358
|
* busca, então a sub-fonte dele não diz (`served_from_cache: null`).
|
|
349
359
|
*/
|
|
350
360
|
export function provenanceServidoresSubordinadas(prov, situacao, folhaDoCache) {
|
|
351
|
-
return withFieldSources(prov, [
|
|
361
|
+
return derivado(withFieldSources(prov, [
|
|
352
362
|
{
|
|
353
363
|
fields: ["servidores", "total", "count", "naoClassificados", "afastadosOuEmTransito"],
|
|
354
364
|
source_url: prov.source_url,
|
|
@@ -363,7 +373,7 @@ export function provenanceServidoresSubordinadas(prov, situacao, folhaDoCache) {
|
|
|
363
373
|
reference_period: ESTRUTURA_VINTAGE.slice(0, 10),
|
|
364
374
|
retrieved_at: ESTRUTURA_VINTAGE,
|
|
365
375
|
},
|
|
366
|
-
]);
|
|
376
|
+
]), NOTA_DERIVACAO_SUBORDINADAS);
|
|
367
377
|
}
|
|
368
378
|
export function registerServidoresTools(server, admBaseUrl) {
|
|
369
379
|
// P1. senado_servidores
|
|
@@ -473,7 +483,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
473
483
|
agruparPor: params.agruparPor,
|
|
474
484
|
topN: params.topN ?? 10,
|
|
475
485
|
}),
|
|
476
|
-
}, prov);
|
|
486
|
+
}, derivadoDeEstatisticas(prov));
|
|
477
487
|
}
|
|
478
488
|
if ((params.modo ?? "resumo") === "detalhe") {
|
|
479
489
|
const limite = params.limite ?? 50;
|
|
@@ -484,7 +494,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
484
494
|
total: itens.length,
|
|
485
495
|
...(itens.length > limite ? { aviso: `Exibindo ${limite} de ${itens.length} registros. Filtre por nome ou use modo=resumo.` } : {}),
|
|
486
496
|
remuneracoes: itens.slice(0, limite).map(resumoRemuneracao),
|
|
487
|
-
}, prov);
|
|
497
|
+
}, provenanceDaRemuneracao(prov, "detalhe"));
|
|
488
498
|
}
|
|
489
499
|
const porFolha = new Map();
|
|
490
500
|
for (const r of itens) {
|
|
@@ -503,7 +513,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
503
513
|
mediaBruta: Math.round((g.totalBruto / g.registros) * 100) / 100,
|
|
504
514
|
}))
|
|
505
515
|
.sort((a, b) => b.totalBruto - a.totalBruto);
|
|
506
|
-
return resultWithProvenance({ ano: params.ano, mes: params.mes, totalRegistros: itens.length, resumo }, prov);
|
|
516
|
+
return resultWithProvenance({ ano: params.ano, mes: params.mes, totalRegistros: itens.length, resumo }, provenanceDaRemuneracao(prov, "resumo"));
|
|
507
517
|
}
|
|
508
518
|
catch (e) {
|
|
509
519
|
return errorFrom(e, "Erro ao consultar remunerações");
|
|
@@ -539,7 +549,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
539
549
|
total: itens.length,
|
|
540
550
|
valorTotal,
|
|
541
551
|
...estatisticasHorasExtras(itens, { agruparPor: params.agruparPor, topN: params.topN ?? 10 }),
|
|
542
|
-
}, prov);
|
|
552
|
+
}, derivadoDeEstatisticas(prov));
|
|
543
553
|
}
|
|
544
554
|
return resultWithProvenance({
|
|
545
555
|
ano: params.ano,
|
|
@@ -548,7 +558,7 @@ export function registerServidoresTools(server, admBaseUrl) {
|
|
|
548
558
|
total: itens.length,
|
|
549
559
|
valorTotal,
|
|
550
560
|
horasExtras: itens.slice(0, limite),
|
|
551
|
-
}, prov);
|
|
561
|
+
}, provenanceDasHorasExtras(prov));
|
|
552
562
|
}
|
|
553
563
|
catch (e) {
|
|
554
564
|
return errorFrom(e, "Erro ao consultar horas extras");
|
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,
|
|
@@ -7,14 +7,14 @@ import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
|
7
7
|
import { upstreamFetch } from "../throttle/upstream.js";
|
|
8
8
|
import { toolError, errorFrom, buildParams, ensureArray, normalizeText } from "../utils/validation.js";
|
|
9
9
|
import { digArrayRoot } from "../utils/upstream-parse.js";
|
|
10
|
-
import { provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
10
|
+
import { NOTA_DERIVACAO_PLACAR_COMISSAO, derivadoSe, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
11
11
|
import { CACHE_DYNAMIC } from "../types.js";
|
|
12
12
|
const VOTACAO_COMISSAO_ROOT = [["VotacoesComissao", "Votacoes", "Votacao"]];
|
|
13
13
|
/** Parse a committee vote item (VotacoesComissao.Votacoes.Votacao[]). */
|
|
14
14
|
export function parseVotacaoComissao(v) {
|
|
15
15
|
const votacao = v.Votacao || v;
|
|
16
16
|
const votosRaw = ensureArray(votacao.Votos?.Voto ?? votacao.votos);
|
|
17
|
-
let totalSim = 0, totalNao = 0, totalAbstencao = 0;
|
|
17
|
+
let totalSim = 0, totalNao = 0, totalAbstencao = 0; // recontagem, usada só se a fonte não publicar
|
|
18
18
|
const votos = votosRaw.map((vt) => {
|
|
19
19
|
const q = normalizeText(vt.QualidadeVoto || vt.DescricaoVoto || vt.voto);
|
|
20
20
|
if (q === "s" || q === "sim")
|
|
@@ -37,10 +37,35 @@ export function parseVotacaoComissao(v) {
|
|
|
37
37
|
reuniao: votacao.CodigoReuniao || null,
|
|
38
38
|
materia: votacao.IdentificacaoMateria || votacao.DescricaoIdentificacaoMateria || null,
|
|
39
39
|
descricao: votacao.DescricaoVotacao || votacao.descricaoVotacao || null,
|
|
40
|
-
totalSim, totalNao, totalAbstencao,
|
|
40
|
+
...placarDaComissao(votacao, { sim: totalSim, nao: totalNao, abstencao: totalAbstencao }),
|
|
41
41
|
votos,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
const totalPublicado = (v) => {
|
|
45
|
+
if (v === undefined || v === null || v === "")
|
|
46
|
+
return null;
|
|
47
|
+
const n = Number(v);
|
|
48
|
+
return Number.isFinite(n) ? n : null;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Committee tally: the source's `TotalVotosSim/Nao/Abstencao` when it publishes them (it does on
|
|
52
|
+
* every vote measured — 395 of 395 at the CCJ on 15/10/2026), the recount of the nominal votes
|
|
53
|
+
* only when all three are missing, then flagged `placarComputado`. Until 3.18.5 the parser always
|
|
54
|
+
* recounted, and 5 of those 395 came out different from the published tally: the source repeats
|
|
55
|
+
* the nominal list in some votes (24/25 counted against 12/13 published) and counts an "A" vote
|
|
56
|
+
* as no abstention in others. The published number is the source's own; the recount is not.
|
|
57
|
+
*/
|
|
58
|
+
export function placarDaComissao(votacao, recontagem) {
|
|
59
|
+
const sim = totalPublicado(votacao?.TotalVotosSim);
|
|
60
|
+
const nao = totalPublicado(votacao?.TotalVotosNao);
|
|
61
|
+
const abstencao = totalPublicado(votacao?.TotalVotosAbstencao);
|
|
62
|
+
if (sim !== null || nao !== null || abstencao !== null) {
|
|
63
|
+
return { totalSim: sim ?? 0, totalNao: nao ?? 0, totalAbstencao: abstencao ?? 0 };
|
|
64
|
+
}
|
|
65
|
+
return { totalSim: recontagem.sim, totalNao: recontagem.nao, totalAbstencao: recontagem.abstencao, placarComputado: true };
|
|
66
|
+
}
|
|
67
|
+
/** `derived` (contract v1.3) only when some item's tally had to be counted from the votes. */
|
|
68
|
+
export const provenanceDaVotacaoComissao = (prov, votacoes) => derivadoSe(prov, votacoes.some((v) => v.placarComputado === true), NOTA_DERIVACAO_PLACAR_COMISSAO);
|
|
44
69
|
/**
|
|
45
70
|
* Filter parsed committee votes locally by reunion date. The upstream serves the full
|
|
46
71
|
* history regardless of the date query params, so the window is enforced in-Worker.
|
|
@@ -66,7 +91,7 @@ export function registerVotacaoComissaoTools(server, baseUrl) {
|
|
|
66
91
|
"`por: comissao` → exige `siglaComissao`; lista as votações daquela comissão. " +
|
|
67
92
|
"`por: senador` → exige `codigoSenador`; lista os votos do senador em comissões (filtro opcional `comissao`). " +
|
|
68
93
|
"`por: materia` → exige `sigla`, `numero` e `ano` (ex.: PL 2630/2020); lista as votações da proposição em comissões (filtro opcional `comissao`). " +
|
|
69
|
-
"Em todos os casos aceita período opcional `dataInicio`/`dataFim` (YYYYMMDD, filtrado pela data da reunião) e retorna `{ por, ...contexto, count, votacoes }`, cada votação com `codigo`, `data`, `comissao`, `reuniao`, `materia`, `descricao`, totais
|
|
94
|
+
"Em todos os casos aceita período opcional `dataInicio`/`dataFim` (YYYYMMDD, filtrado pela data da reunião) e retorna `{ por, ...contexto, count, votacoes }`, cada votação com `codigo`, `data`, `comissao`, `reuniao`, `materia`, `descricao`, totais publicados pela fonte (`totalSim`/`totalNao`/`totalAbstencao`; contados dos votos, com `placarComputado: true`, só quando a fonte não os traz) e `votos` (senador, partido, voto). Sem paginação. Períodos amplos podem estourar o tempo-limite da consulta (erro de timeout): prefira janelas de até 3 meses e divida períodos maiores em chamadas sucessivas. " +
|
|
70
95
|
"Obtenha siglas via `senado_listar_comissoes`, `codigoSenador` via `senado_listar_senadores`; para votações no plenário use `senado_votos_materia`. Atenção: o `codigo` de cada votação de comissão pertence a um espaço de numeração próprio e NÃO é válido em `senado_obter_votacao` (que é exclusivo de plenário) — podem coincidir numericamente, mas apontam para votações diferentes.", {
|
|
71
96
|
por: z.enum(["comissao", "senador", "materia"]).optional().default("comissao").describe("Eixo da consulta: comissao, senador ou materia"),
|
|
72
97
|
siglaComissao: z.string().min(2).optional().describe("Sigla da comissão (obrigatório quando por=comissao; ex: CCJ, CAE)"),
|
|
@@ -97,7 +122,7 @@ export function registerVotacaoComissaoTools(server, baseUrl) {
|
|
|
97
122
|
? `${params.dataInicio}/${params.dataFim}` : undefined,
|
|
98
123
|
retrieved_at: fetchedAt,
|
|
99
124
|
});
|
|
100
|
-
return resultWithProvenance({ por, codigoSenador: params.codigoSenador, count: votacoes.length, votacoes }, prov);
|
|
125
|
+
return resultWithProvenance({ por, codigoSenador: params.codigoSenador, count: votacoes.length, votacoes }, provenanceDaVotacaoComissao(prov, votacoes));
|
|
101
126
|
}
|
|
102
127
|
if (por === "materia") {
|
|
103
128
|
if (!params.sigla || !params.numero || !params.ano) {
|
|
@@ -117,7 +142,7 @@ export function registerVotacaoComissaoTools(server, baseUrl) {
|
|
|
117
142
|
reference_period: String(params.ano),
|
|
118
143
|
retrieved_at: fetchedAt,
|
|
119
144
|
});
|
|
120
|
-
return resultWithProvenance({ por, materia: `${sigla} ${params.numero}/${params.ano}`, count: votacoes.length, votacoes }, prov);
|
|
145
|
+
return resultWithProvenance({ por, materia: `${sigla} ${params.numero}/${params.ano}`, count: votacoes.length, votacoes }, provenanceDaVotacaoComissao(prov, votacoes));
|
|
121
146
|
}
|
|
122
147
|
// por === "comissao" (padrão)
|
|
123
148
|
if (!params.siglaComissao)
|
|
@@ -136,7 +161,7 @@ export function registerVotacaoComissaoTools(server, baseUrl) {
|
|
|
136
161
|
? `${params.dataInicio}/${params.dataFim}` : undefined,
|
|
137
162
|
retrieved_at: fetchedAt,
|
|
138
163
|
});
|
|
139
|
-
return resultWithProvenance({ por, siglaComissao: sigla, count: votacoes.length, votacoes }, prov);
|
|
164
|
+
return resultWithProvenance({ por, siglaComissao: sigla, count: votacoes.length, votacoes }, provenanceDaVotacaoComissao(prov, votacoes));
|
|
140
165
|
}
|
|
141
166
|
catch (e) {
|
|
142
167
|
return errorFrom(e, "Erro ao obter votações em comissões");
|
package/dist/tools/votacoes.js
CHANGED
|
@@ -11,7 +11,7 @@ import { cachedFetchWithMeta } from "../cache/manager.js";
|
|
|
11
11
|
import { upstreamFetch } from "../throttle/upstream.js";
|
|
12
12
|
import { toolError, errorFrom, buildParams, ensureArray } from "../utils/validation.js";
|
|
13
13
|
import { computarPlacar } from "../utils/placar.js";
|
|
14
|
-
import { provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
14
|
+
import { NOTA_DERIVACAO_PLACAR, derivadoSe, provenanceFor, resultWithProvenance } from "../utils/provenance.js";
|
|
15
15
|
import { CACHE_ON_DEMAND } from "../types.js";
|
|
16
16
|
/** Convert YYYYMMDD → YYYY-MM-DD (required by /votacao endpoint). */
|
|
17
17
|
export function toISODate(yyyymmdd) {
|
|
@@ -42,6 +42,12 @@ export function expandirResultado(code) {
|
|
|
42
42
|
const raw = String(code).trim();
|
|
43
43
|
return { resultado: RESULTADO_VOTACAO[raw.toUpperCase()] ?? raw, resultadoCodigo: raw };
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Provenance of a plenary vote response (contract v1.3): `derived` only when at least one item's
|
|
47
|
+
* tally was counted from the nominal votes (`placarComputado`); totals the source publishes are
|
|
48
|
+
* copied and do not mark the block.
|
|
49
|
+
*/
|
|
50
|
+
export const provenanceDaVotacao = (prov, votacoes) => derivadoSe(prov, votacoes.some((v) => v.placarComputado === true), NOTA_DERIVACAO_PLACAR);
|
|
45
51
|
/** Parse a single vote item from the /votacao endpoint (flat camelCase). */
|
|
46
52
|
export function parseVotacaoItem(v, includeVotos = false) {
|
|
47
53
|
const votosRaw = Array.isArray(v.votos) ? v.votos : [];
|
|
@@ -138,8 +144,8 @@ export function registerVotacoesTools(server, baseUrl) {
|
|
|
138
144
|
retrieved_at: fetchedAt,
|
|
139
145
|
});
|
|
140
146
|
if (votacoes.length === 1)
|
|
141
|
-
return resultWithProvenance(votacoes[0], prov);
|
|
142
|
-
return resultWithProvenance({ codigoSessao: codigo, count: votacoes.length, votacoes }, prov);
|
|
147
|
+
return resultWithProvenance(votacoes[0], provenanceDaVotacao(prov, votacoes));
|
|
148
|
+
return resultWithProvenance({ codigoSessao: codigo, count: votacoes.length, votacoes }, provenanceDaVotacao(prov, votacoes));
|
|
143
149
|
}
|
|
144
150
|
// PASSO 2 — vazio não é resposta: o número pode ser do OUTRO espaço.
|
|
145
151
|
// Até 24/09/2026 o handler paravaaqui e devolvia `{count: 0}` — para
|
|
@@ -152,11 +158,11 @@ export function registerVotacoesTools(server, baseUrl) {
|
|
|
152
158
|
const achado = acharPorCodigoVotacao(cru, codigo);
|
|
153
159
|
if (achado) {
|
|
154
160
|
const votacao = parseVotacaoItem(achado, true);
|
|
155
|
-
return resultWithProvenance(votacao, provenanceFor("SENADO_LEGIS", baseUrl, "/votacao", {
|
|
161
|
+
return resultWithProvenance(votacao, provenanceDaVotacao(provenanceFor("SENADO_LEGIS", baseUrl, "/votacao", {
|
|
156
162
|
dataset_id: `${CAMPO_VOTACAO}=${codigo}`,
|
|
157
163
|
reference_period: votacao.data || undefined,
|
|
158
164
|
retrieved_at: fetchedAtJanela,
|
|
159
|
-
}));
|
|
165
|
+
}), [votacao]));
|
|
160
166
|
}
|
|
161
167
|
// PASSO 3 — não está em nenhum dos dois espaços: ausência TIPADA.
|
|
162
168
|
const ondeProcurou = params.ano
|
|
@@ -187,7 +193,7 @@ export function registerVotacoesTools(server, baseUrl) {
|
|
|
187
193
|
reference_period: votacoes[0]?.data || undefined,
|
|
188
194
|
retrieved_at: fetchedAt,
|
|
189
195
|
});
|
|
190
|
-
return resultWithProvenance({ codigoMateria: params.codigoMateria, count: votacoes.length, votacoes }, prov);
|
|
196
|
+
return resultWithProvenance({ codigoMateria: params.codigoMateria, count: votacoes.length, votacoes }, provenanceDaVotacao(prov, votacoes));
|
|
191
197
|
}
|
|
192
198
|
catch (e) {
|
|
193
199
|
return errorFrom(e, "Erro ao obter votações da matéria");
|
|
@@ -235,7 +241,7 @@ export function registerVotacoesTools(server, baseUrl) {
|
|
|
235
241
|
reference_period: di && df ? `${di}/${df}` : di || df || undefined,
|
|
236
242
|
retrieved_at: fetchedAt,
|
|
237
243
|
});
|
|
238
|
-
return resultWithProvenance({ count: votacoes.length, votacoes }, prov);
|
|
244
|
+
return resultWithProvenance({ count: votacoes.length, votacoes }, provenanceDaVotacao(prov, votacoes));
|
|
239
245
|
}
|
|
240
246
|
catch (e) {
|
|
241
247
|
return errorFrom(e, "Erro na busca de votações");
|
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,56 @@ 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
|
+
* Notas fixas dos demais cálculos do servidor (contrato v1.3, 3.19.0). Uma por TIPO de
|
|
281
|
+
* cálculo, não por tool: o leitor precisa saber o que foi calculado, não onde.
|
|
282
|
+
*/
|
|
283
|
+
/** Somas, contagens e médias por grupo (modos agregados, `valorTotal`, contagens por unidade/partido/UF). */
|
|
284
|
+
export const NOTA_DERIVACAO_SOMAS = "Totais, contagens e médias por grupo calculados pelo servidor a partir dos registros da fonte; os valores individuais permanecem os originais do Senado Federal.";
|
|
285
|
+
/** `bruto` das remunerações: soma dos sete componentes da folha. */
|
|
286
|
+
export const NOTA_DERIVACAO_BRUTO = "A remuneração bruta é a soma, feita pelo servidor, dos sete componentes publicados pela fonte, que permanecem os originais.";
|
|
287
|
+
/** Servidores sob uma unidade: classificação de cada lotação no organograma, feita pelo servidor. */
|
|
288
|
+
export const NOTA_DERIVACAO_SUBORDINADAS = "Servidores atribuídos a cada unidade e contados pelo servidor, cruzando a lotação de cada um com o organograma publicado; o total é um piso (lotações não reconhecidas ficam de fora).";
|
|
289
|
+
/** Placar de votação do plenário contado dos votos nominais (votação aberta sem totais na fonte). */
|
|
290
|
+
export const NOTA_DERIVACAO_PLACAR = "Totais de votos contados pelo servidor a partir dos votos nominais, porque a fonte não publica o placar desta votação.";
|
|
291
|
+
/** Placar de votação de comissão contado dos votos, quando a fonte não traz os totais. */
|
|
292
|
+
export const NOTA_DERIVACAO_PLACAR_COMISSAO = "Totais de votos de pelo menos uma votação contados pelo servidor a partir dos votos de cada parlamentar, porque a fonte não publicou o placar dela.";
|
|
293
|
+
/** Percentuais (e o total, quando ausente) das consultas públicas do e-Cidadania. */
|
|
294
|
+
export const NOTA_DERIVACAO_PERCENTUAIS = "Percentuais de sim e não, e o total de votos quando a fonte não o publica, calculados pelo servidor a partir dos votos da fonte.";
|
|
295
|
+
/** Polarização, consenso e ordem por participação no e-Cidadania. */
|
|
296
|
+
export const NOTA_DERIVACAO_RANKING_ECIDADANIA = "Polarização, consenso e ordem por participação calculados pelo servidor a partir dos votos e apoios publicados pela fonte.";
|
|
297
|
+
/** Data de criação estimada das ideias legislativas. */
|
|
298
|
+
export const NOTA_DERIVACAO_DATA_ESTIMADA = "A data de criação é estimada pelo servidor (fim do prazo de apoio menos 120 dias); a fonte não a publica.";
|
|
299
|
+
/** Legislatura sem número na fonte: o próprio número é o padrão do servidor. */
|
|
300
|
+
export const NOTA_DERIVACAO_LEGISLATURA_PADRAO = "A fonte não informou a legislatura: o servidor assume a 57ª (2023–2027) e calcula o período e as datas.";
|
|
301
|
+
/** Período e datas da legislatura derivados do número dela. */
|
|
302
|
+
export const NOTA_DERIVACAO_LEGISLATURA = "Período e datas da legislatura calculados pelo servidor a partir do número publicado pela fonte (mandatos de quatro anos).";
|
|
303
|
+
/**
|
|
304
|
+
* Marca o bloco como derivado (contrato v1.3): `derived: true` + a nota dada. Aplique por
|
|
305
|
+
* último, sobre o bloco já completo (inclusive o de `withFieldSources`); o envelope é
|
|
306
|
+
* re-validado inteiro no contrato (build).
|
|
307
|
+
*/
|
|
308
|
+
export function derivado(prov, nota) {
|
|
309
|
+
return provenanceContext.build({
|
|
310
|
+
...prov,
|
|
311
|
+
derived: true,
|
|
312
|
+
derivation_note: nota,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** `derivado` só quando `calculou`; senão o bloco volta intacto (sem `derived`). */
|
|
316
|
+
export function derivadoSe(prov, calculou, nota) {
|
|
317
|
+
return calculou ? derivado(prov, nota) : prov;
|
|
318
|
+
}
|
|
319
|
+
/** `derivado` com a nota dos modos `estatisticas`. */
|
|
320
|
+
export function derivadoDeEstatisticas(prov) {
|
|
321
|
+
return derivado(prov, NOTA_DERIVACAO_ESTATISTICAS);
|
|
322
|
+
}
|
|
271
323
|
/** Rodapé de texto do contrato: fonte · url · vintage · extração, licença e aviso ao leitor. */
|
|
272
324
|
export const provenanceFooter = (p) => provenanceContext.footer(p);
|
|
273
325
|
/**
|
package/dist/version.js
CHANGED