bcb-br-mcp 1.3.4 → 1.4.1
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 +60 -4
- package/dist/cambio.d.ts +72 -0
- package/dist/cambio.d.ts.map +1 -0
- package/dist/cambio.js +284 -0
- package/dist/cambio.js.map +1 -0
- package/dist/catalog.d.ts +107 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +206 -0
- package/dist/catalog.js.map +1 -0
- package/dist/focus.d.ts +134 -0
- package/dist/focus.d.ts.map +1 -0
- package/dist/focus.js +683 -0
- package/dist/focus.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -346
- package/dist/index.js.map +1 -1
- package/dist/olinda.d.ts +59 -0
- package/dist/olinda.d.ts.map +1 -0
- package/dist/olinda.js +98 -0
- package/dist/olinda.js.map +1 -0
- package/dist/register.d.ts +61 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +116 -0
- package/dist/register.js.map +1 -0
- package/dist/shared.d.ts +89 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +151 -0
- package/dist/shared.js.map +1 -0
- package/dist/tools.d.ts +8128 -280
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +232 -153
- package/dist/tools.js.map +1 -1
- package/package.json +12 -11
- package/dist/worker.d.ts +0 -20
- package/dist/worker.d.ts.map +0 -1
- package/dist/worker.js +0 -240
- package/dist/worker.js.map +0 -1
package/dist/focus.js
ADDED
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expectativas de Mercado (Focus) sob contrato consolidado.
|
|
3
|
+
*
|
|
4
|
+
* Desenho aprovado pelo decisor (arbitragem 3 + fronteira concreta da sessão de
|
|
5
|
+
* D3): NÃO espelhar os recursos OData. Três tools em vez de treze:
|
|
6
|
+
*
|
|
7
|
+
* - `bcb_focus_expectativas` — as cinco expectativas de calendário (mensal,
|
|
8
|
+
* trimestral, anual, inflação em 12 e em 24 meses) numa tool só, com
|
|
9
|
+
* `horizonte` como parâmetro. Os dois horizontes rolantes não têm data de
|
|
10
|
+
* referência (o alvo é "os próximos N meses", não um mês do calendário) e têm
|
|
11
|
+
* o campo `suavizada`, que os de calendário não têm: `referencia` é obrigatória
|
|
12
|
+
* nos três de calendário e recusada nos rolantes, com erro que diz o que usar.
|
|
13
|
+
* - `bcb_focus_selic` — separada porque o eixo temporal é a REUNIÃO do Copom, não
|
|
14
|
+
* o calendário. Consolidar aqui esconderia a diferença que importa.
|
|
15
|
+
* - `bcb_focus_referencias` — auxiliar de descoberta. Sem ela o agente adivinha
|
|
16
|
+
* strings de referência ("2026"? "12/2026"? "4/2026"?), que é o modo mais
|
|
17
|
+
* comum de a consulta voltar vazia.
|
|
18
|
+
*
|
|
19
|
+
* Top 5 é SINALIZADOR (`top5: true`), não tool espelhada — e não existe para
|
|
20
|
+
* toda combinação: a tabela de combinações válidas mora em `RECURSOS` e o erro
|
|
21
|
+
* diz qual usar.
|
|
22
|
+
*
|
|
23
|
+
* `ExpectativasMercadoInstituicoes` jamais entra (desativado pelo BCB por
|
|
24
|
+
* confidencialidade).
|
|
25
|
+
*/
|
|
26
|
+
import { EXPECTATIVAS_ODATA, OLINDA_MAX_LINHAS, consultarOData, hojeIso, montarUrlOData, numeroOuNulo, odataString, paraIso, somarDiasIso, textoOuNulo } from "./olinda.js";
|
|
27
|
+
import { erroResult, leituraRemota, mensagemDeErro, structuredResult } from "./shared.js";
|
|
28
|
+
/** Janela de coleta padrão quando o usuário não informa datas. */
|
|
29
|
+
const JANELA_PADRAO_DIAS = 30;
|
|
30
|
+
/**
|
|
31
|
+
* Mapa horizonte -> recurso OData. É o ÚNICO lugar que conhece os nomes dos
|
|
32
|
+
* recursos: se a fonte renomear ou deixar de oferecer Top 5 em algum horizonte, a
|
|
33
|
+
* correção é uma linha aqui.
|
|
34
|
+
*
|
|
35
|
+
* Os nomes foram lidos do documento de serviço do OData contra a origem, não
|
|
36
|
+
* supostos. Repare na irregularidade da fonte, que é real e não erro de digitação:
|
|
37
|
+
* o mensal é `ExpectativaMercadoMensais` (singular) e o Top 5 trimestral é
|
|
38
|
+
* `ExpectativaMercadoTop5Trimestral` (singular nas duas pontas), enquanto todo o
|
|
39
|
+
* resto é plural.
|
|
40
|
+
*/
|
|
41
|
+
export const RECURSOS = {
|
|
42
|
+
mensal: {
|
|
43
|
+
consenso: "ExpectativaMercadoMensais",
|
|
44
|
+
top5: "ExpectativasMercadoTop5Mensais",
|
|
45
|
+
campoReferencia: "DataReferencia",
|
|
46
|
+
formatoReferencia: "MM/yyyy (ex.: 09/2026)"
|
|
47
|
+
},
|
|
48
|
+
trimestral: {
|
|
49
|
+
consenso: "ExpectativasMercadoTrimestrais",
|
|
50
|
+
top5: "ExpectativaMercadoTop5Trimestral",
|
|
51
|
+
campoReferencia: "DataReferencia",
|
|
52
|
+
formatoReferencia: "T/yyyy (ex.: 3/2026)"
|
|
53
|
+
},
|
|
54
|
+
anual: {
|
|
55
|
+
consenso: "ExpectativasMercadoAnuais",
|
|
56
|
+
top5: "ExpectativasMercadoTop5Anuais",
|
|
57
|
+
campoReferencia: "DataReferencia",
|
|
58
|
+
formatoReferencia: "yyyy (ex.: 2027)"
|
|
59
|
+
},
|
|
60
|
+
inflacao_12m: {
|
|
61
|
+
consenso: "ExpectativasMercadoInflacao12Meses",
|
|
62
|
+
top5: "ExpectativasMercadoTop5Inflacao12Meses",
|
|
63
|
+
campoReferencia: null,
|
|
64
|
+
formatoReferencia: null
|
|
65
|
+
},
|
|
66
|
+
inflacao_24m: {
|
|
67
|
+
consenso: "ExpectativasMercadoInflacao24Meses",
|
|
68
|
+
top5: "ExpectativasMercadoTop5Inflacao24Meses",
|
|
69
|
+
campoReferencia: null,
|
|
70
|
+
formatoReferencia: null
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/** Recurso de Selic, fora do mapa de horizontes porque o eixo é a reunião do Copom. */
|
|
74
|
+
export const RECURSO_SELIC = { consenso: "ExpectativasMercadoSelic", top5: "ExpectativasMercadoTop5Selic" };
|
|
75
|
+
const HORIZONTES = Object.keys(RECURSOS);
|
|
76
|
+
const HORIZONTES_ROLANTES = ["inflacao_12m", "inflacao_24m"];
|
|
77
|
+
/**
|
|
78
|
+
* Lê o primeiro nome presente. Existe porque a fonte NÃO é uniforme na caixa dos
|
|
79
|
+
* campos: `ExpectativasMercadoTop5Selic` publica `indicador`, `reuniao`, `media`,
|
|
80
|
+
* `mediana`, `desvioPadrao`, `minimo` e `maximo` em caixa baixa, enquanto todos os
|
|
81
|
+
* outros doze recursos usam inicial maiúscula. Verificado contra a origem.
|
|
82
|
+
*/
|
|
83
|
+
function primeiroCampo(linha, ...nomes) {
|
|
84
|
+
for (const nome of nomes)
|
|
85
|
+
if (linha[nome] !== undefined)
|
|
86
|
+
return linha[nome];
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Normaliza uma linha do OData. Defensivo de propósito: os recursos não têm o
|
|
91
|
+
* mesmo conjunto de campos, o alvo aparece como `DataReferencia` (calendário) ou
|
|
92
|
+
* `Reuniao` (Selic), e a caixa dos nomes varia entre recursos.
|
|
93
|
+
*/
|
|
94
|
+
export function normalizarExpectativa(linha) {
|
|
95
|
+
const normalizada = {
|
|
96
|
+
indicador: textoOuNulo(primeiroCampo(linha, "Indicador", "indicador")),
|
|
97
|
+
indicadorDetalhe: textoOuNulo(primeiroCampo(linha, "IndicadorDetalhe", "indicadorDetalhe")),
|
|
98
|
+
coletadoEm: textoOuNulo(primeiroCampo(linha, "Data", "data")),
|
|
99
|
+
referencia: textoOuNulo(primeiroCampo(linha, "DataReferencia", "dataReferencia")) ??
|
|
100
|
+
textoOuNulo(primeiroCampo(linha, "Reuniao", "reuniao")),
|
|
101
|
+
media: numeroOuNulo(primeiroCampo(linha, "Media", "media")),
|
|
102
|
+
mediana: numeroOuNulo(primeiroCampo(linha, "Mediana", "mediana")),
|
|
103
|
+
desvioPadrao: numeroOuNulo(primeiroCampo(linha, "DesvioPadrao", "desvioPadrao")),
|
|
104
|
+
minimo: numeroOuNulo(primeiroCampo(linha, "Minimo", "minimo")),
|
|
105
|
+
maximo: numeroOuNulo(primeiroCampo(linha, "Maximo", "maximo")),
|
|
106
|
+
respondentes: numeroOuNulo(primeiroCampo(linha, "numeroRespondentes", "NumeroRespondentes")),
|
|
107
|
+
baseCalculo: numeroOuNulo(primeiroCampo(linha, "baseCalculo", "BaseCalculo"))
|
|
108
|
+
};
|
|
109
|
+
const suavizada = primeiroCampo(linha, "Suavizada", "suavizada");
|
|
110
|
+
if (suavizada !== undefined) {
|
|
111
|
+
const bruto = textoOuNulo(suavizada);
|
|
112
|
+
normalizada.suavizada = bruto === null ? null : bruto.toUpperCase().startsWith("S");
|
|
113
|
+
}
|
|
114
|
+
const tipoCalculo = primeiroCampo(linha, "tipoCalculo", "TipoCalculo");
|
|
115
|
+
if (tipoCalculo !== undefined)
|
|
116
|
+
normalizada.tipoCalculo = textoOuNulo(tipoCalculo);
|
|
117
|
+
const coeficiente = primeiroCampo(linha, "coeficienteVariacao", "CoeficienteVariacao");
|
|
118
|
+
if (coeficiente !== undefined)
|
|
119
|
+
normalizada.coeficienteVariacao = numeroOuNulo(coeficiente);
|
|
120
|
+
return normalizada;
|
|
121
|
+
}
|
|
122
|
+
/** Ordena da coleta mais recente para a mais antiga (client-side; ver olinda.ts). */
|
|
123
|
+
function maisRecentePrimeiro(a, b) {
|
|
124
|
+
return (b.coletadoEm ?? "").localeCompare(a.coletadoEm ?? "") || (a.referencia ?? "").localeCompare(b.referencia ?? "");
|
|
125
|
+
}
|
|
126
|
+
function resolverJanela(dataInicial, dataFinal) {
|
|
127
|
+
const fim = dataFinal ? paraIso(dataFinal) : hojeIso();
|
|
128
|
+
if (!fim)
|
|
129
|
+
return { erro: `dataFinal inválida: "${dataFinal}". Use yyyy-MM-dd ou dd/MM/yyyy.` };
|
|
130
|
+
const inicio = dataInicial ? paraIso(dataInicial) : somarDiasIso(fim, -JANELA_PADRAO_DIAS);
|
|
131
|
+
if (!inicio)
|
|
132
|
+
return { erro: `dataInicial inválida: "${dataInicial}". Use yyyy-MM-dd ou dd/MM/yyyy.` };
|
|
133
|
+
if (inicio > fim)
|
|
134
|
+
return { erro: `A janela está invertida: dataInicial (${inicio}) é posterior a dataFinal (${fim}).` };
|
|
135
|
+
return { dataInicial: inicio, dataFinal: fim, padrao: !dataInicial && !dataFinal };
|
|
136
|
+
}
|
|
137
|
+
export async function handleFocusExpectativas(args, timeoutMs, maxRetries) {
|
|
138
|
+
const recurso = RECURSOS[args.horizonte];
|
|
139
|
+
if (!recurso) {
|
|
140
|
+
return erroResult(`Horizonte inválido: "${args.horizonte}". Use um destes: ${HORIZONTES.join(", ")}. ` +
|
|
141
|
+
"Para expectativa de Selic por reunião do Copom, use bcb_focus_selic.");
|
|
142
|
+
}
|
|
143
|
+
const rolante = HORIZONTES_ROLANTES.includes(args.horizonte);
|
|
144
|
+
// A fronteira consolidar x separar, tornada explícita no contrato: os
|
|
145
|
+
// horizontes rolantes não têm alvo de calendário, e mandar `referencia` neles
|
|
146
|
+
// é sinal de que o usuário quis outro horizonte.
|
|
147
|
+
if (rolante && args.referencia !== undefined) {
|
|
148
|
+
return erroResult(`O horizonte "${args.horizonte}" é rolante (os próximos ${args.horizonte === "inflacao_12m" ? 12 : 24} ` +
|
|
149
|
+
"meses a partir de cada coleta) e não aceita `referencia`. Para expectativa de um mês, trimestre ou ano " +
|
|
150
|
+
"específico, use horizonte mensal, trimestral ou anual com a `referencia` correspondente.");
|
|
151
|
+
}
|
|
152
|
+
if (!rolante && args.referencia === undefined) {
|
|
153
|
+
return erroResult(`O horizonte "${args.horizonte}" exige \`referencia\` no formato ${recurso.formatoReferencia}. ` +
|
|
154
|
+
"Use bcb_focus_referencias para ver as referências disponíveis para o indicador.");
|
|
155
|
+
}
|
|
156
|
+
if (!rolante && args.suavizada !== undefined) {
|
|
157
|
+
return erroResult("`suavizada` só existe nos horizontes rolantes (inflacao_12m, inflacao_24m), onde a fonte publica a " +
|
|
158
|
+
"série suavizada e a não suavizada.");
|
|
159
|
+
}
|
|
160
|
+
if (args.top5 === true && recurso.top5 === null) {
|
|
161
|
+
const comTop5 = HORIZONTES.filter(h => RECURSOS[h].top5 !== null);
|
|
162
|
+
return erroResult(`A fonte não publica Top 5 para o horizonte "${args.horizonte}". Há Top 5 em: ${comTop5.join(", ")} ` +
|
|
163
|
+
"(e na Selic, por bcb_focus_selic com top5: true).");
|
|
164
|
+
}
|
|
165
|
+
const janela = resolverJanela(args.dataInicial, args.dataFinal);
|
|
166
|
+
if ("erro" in janela)
|
|
167
|
+
return erroResult(janela.erro);
|
|
168
|
+
// Filtro por construção: sem filtro, a consulta ao Olinda não completa.
|
|
169
|
+
const filtro = [
|
|
170
|
+
`Indicador eq ${odataString(args.indicador)}`,
|
|
171
|
+
`Data ge ${odataString(janela.dataInicial)}`,
|
|
172
|
+
`Data le ${odataString(janela.dataFinal)}`
|
|
173
|
+
];
|
|
174
|
+
if (args.referencia !== undefined && recurso.campoReferencia) {
|
|
175
|
+
filtro.push(`${recurso.campoReferencia} eq ${odataString(args.referencia)}`);
|
|
176
|
+
}
|
|
177
|
+
if (args.suavizada !== undefined)
|
|
178
|
+
filtro.push(`Suavizada eq ${odataString(args.suavizada ? "S" : "N")}`);
|
|
179
|
+
const url = montarUrlOData(EXPECTATIVAS_ODATA, {
|
|
180
|
+
recurso: args.top5 === true ? recurso.top5 : recurso.consenso,
|
|
181
|
+
filtro,
|
|
182
|
+
top: OLINDA_MAX_LINHAS
|
|
183
|
+
});
|
|
184
|
+
try {
|
|
185
|
+
const linhas = await consultarOData(url, timeoutMs, maxRetries);
|
|
186
|
+
const limite = args.limite ?? 50;
|
|
187
|
+
const normalizadas = linhas.map(normalizarExpectativa).sort(maisRecentePrimeiro);
|
|
188
|
+
const expectativas = normalizadas.slice(0, limite);
|
|
189
|
+
const payload = {
|
|
190
|
+
indicador: args.indicador,
|
|
191
|
+
horizonte: args.horizonte,
|
|
192
|
+
base: args.top5 === true ? "top5" : "consenso",
|
|
193
|
+
filtro: {
|
|
194
|
+
referencia: args.referencia ?? null,
|
|
195
|
+
dataInicial: janela.dataInicial,
|
|
196
|
+
dataFinal: janela.dataFinal,
|
|
197
|
+
janelaPadrao: janela.padrao,
|
|
198
|
+
suavizada: args.suavizada ?? null
|
|
199
|
+
},
|
|
200
|
+
// Contagem client-side: a fonte ignora `$count=true`.
|
|
201
|
+
totalRegistros: normalizadas.length,
|
|
202
|
+
expectativas,
|
|
203
|
+
urlConsulta: url,
|
|
204
|
+
consultadoEm: new Date().toISOString()
|
|
205
|
+
};
|
|
206
|
+
if (expectativas.length < normalizadas.length) {
|
|
207
|
+
payload.observacao = `Exibindo ${expectativas.length} de ${normalizadas.length} coletas; aumente 'limite' ou estreite a janela.`;
|
|
208
|
+
}
|
|
209
|
+
if (normalizadas.length === 0) {
|
|
210
|
+
payload.observacao =
|
|
211
|
+
"Nenhuma expectativa nesta janela. Confirme o nome do indicador e a referência com " +
|
|
212
|
+
"bcb_focus_referencias — a fonte é sensível ao texto exato — e lembre que a janela padrão é de " +
|
|
213
|
+
`${JANELA_PADRAO_DIAS} dias.`;
|
|
214
|
+
}
|
|
215
|
+
return structuredResult(payload);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
return erroResult(`Erro ao consultar expectativas do Focus: ${mensagemDeErro(error)}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export async function handleFocusSelic(args, timeoutMs, maxRetries) {
|
|
222
|
+
const janela = resolverJanela(args.dataInicial, args.dataFinal);
|
|
223
|
+
if ("erro" in janela)
|
|
224
|
+
return erroResult(janela.erro);
|
|
225
|
+
const filtro = [`Data ge ${odataString(janela.dataInicial)}`, `Data le ${odataString(janela.dataFinal)}`];
|
|
226
|
+
if (args.reuniao !== undefined)
|
|
227
|
+
filtro.push(`Reuniao eq ${odataString(args.reuniao)}`);
|
|
228
|
+
const url = montarUrlOData(EXPECTATIVAS_ODATA, {
|
|
229
|
+
recurso: args.top5 === true ? RECURSO_SELIC.top5 : RECURSO_SELIC.consenso,
|
|
230
|
+
filtro,
|
|
231
|
+
top: OLINDA_MAX_LINHAS
|
|
232
|
+
});
|
|
233
|
+
try {
|
|
234
|
+
const linhas = await consultarOData(url, timeoutMs, maxRetries);
|
|
235
|
+
const limite = args.limite ?? 50;
|
|
236
|
+
const normalizadas = linhas.map(normalizarExpectativa).sort(maisRecentePrimeiro);
|
|
237
|
+
const expectativas = normalizadas.slice(0, limite);
|
|
238
|
+
const payload = {
|
|
239
|
+
base: args.top5 === true ? "top5" : "consenso",
|
|
240
|
+
filtro: {
|
|
241
|
+
reuniao: args.reuniao ?? null,
|
|
242
|
+
dataInicial: janela.dataInicial,
|
|
243
|
+
dataFinal: janela.dataFinal,
|
|
244
|
+
janelaPadrao: janela.padrao
|
|
245
|
+
},
|
|
246
|
+
totalRegistros: normalizadas.length,
|
|
247
|
+
expectativas,
|
|
248
|
+
urlConsulta: url,
|
|
249
|
+
consultadoEm: new Date().toISOString(),
|
|
250
|
+
observacaoEixo: "O eixo desta tool é a REUNIÃO do Copom (`referencia` no formato R1/2026), não o calendário — é por " +
|
|
251
|
+
"isso que ela é separada de bcb_focus_expectativas."
|
|
252
|
+
};
|
|
253
|
+
if (expectativas.length < normalizadas.length) {
|
|
254
|
+
payload.observacao = `Exibindo ${expectativas.length} de ${normalizadas.length} coletas; aumente 'limite' ou estreite a janela.`;
|
|
255
|
+
}
|
|
256
|
+
if (normalizadas.length === 0) {
|
|
257
|
+
payload.observacao =
|
|
258
|
+
"Nenhuma expectativa de Selic nesta janela. Reuniões usam o formato R1/2026 (1ª reunião de 2026); a " +
|
|
259
|
+
`janela padrão é de ${JANELA_PADRAO_DIAS} dias.`;
|
|
260
|
+
}
|
|
261
|
+
return structuredResult(payload);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return erroResult(`Erro ao consultar expectativas de Selic: ${mensagemDeErro(error)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const ESCOPOS = [...HORIZONTES, "selic"];
|
|
268
|
+
/** Janela de coleta da descoberta. O Focus é coletado em todo dia útil, então
|
|
269
|
+
* quinze dias sempre contêm coletas — e a lista de referências muda devagar. */
|
|
270
|
+
const JANELA_REFERENCIAS_DIAS = 15;
|
|
271
|
+
function planoDoEscopo(escopo) {
|
|
272
|
+
if (escopo === "selic") {
|
|
273
|
+
return {
|
|
274
|
+
escopo,
|
|
275
|
+
tool: "bcb_focus_selic",
|
|
276
|
+
recurso: RECURSO_SELIC.consenso,
|
|
277
|
+
campoReferencia: "Reuniao",
|
|
278
|
+
formatoReferencia: "RN/yyyy (ex.: R1/2027 = 1ª reunião do Copom de 2027)",
|
|
279
|
+
temTop5: true
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const recurso = RECURSOS[escopo];
|
|
283
|
+
return {
|
|
284
|
+
escopo,
|
|
285
|
+
tool: "bcb_focus_expectativas",
|
|
286
|
+
recurso: recurso.consenso,
|
|
287
|
+
campoReferencia: recurso.campoReferencia,
|
|
288
|
+
formatoReferencia: recurso.formatoReferencia,
|
|
289
|
+
temTop5: recurso.top5 !== null
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Ordena referências em ordem CRONOLÓGICA, não lexicográfica. Sem isso o mensal
|
|
294
|
+
* sai "01/2027, 01/2028, 02/2027", que é ruído para quem lê a lista. Os quatro
|
|
295
|
+
* formatos da fonte reduzem ao mesmo par (ano, posição dentro do ano):
|
|
296
|
+
* `MM/yyyy`, `T/yyyy`, `RN/yyyy` e `yyyy`.
|
|
297
|
+
*/
|
|
298
|
+
function chaveCronologica(referencia) {
|
|
299
|
+
const composta = /^R?(\d{1,2})\/(\d{4})$/.exec(referencia);
|
|
300
|
+
if (composta)
|
|
301
|
+
return [Number(composta[2]), Number(composta[1])];
|
|
302
|
+
if (/^\d{4}$/.test(referencia))
|
|
303
|
+
return [Number(referencia), 0];
|
|
304
|
+
return [Number.MAX_SAFE_INTEGER, 0];
|
|
305
|
+
}
|
|
306
|
+
function ordenarReferencias(referencias) {
|
|
307
|
+
return [...referencias].sort((a, b) => {
|
|
308
|
+
const [anoA, posA] = chaveCronologica(a);
|
|
309
|
+
const [anoB, posB] = chaveCronologica(b);
|
|
310
|
+
return anoA - anoB || posA - posB || a.localeCompare(b);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Descobre os textos EXATOS de indicador e de referência, por escopo.
|
|
315
|
+
*
|
|
316
|
+
* O parâmetro chama-se `escopo`, e não `horizonte`, de propósito: os cinco
|
|
317
|
+
* horizontes de `bcb_focus_expectativas` mais a Selic — cujo eixo é a reunião do
|
|
318
|
+
* Copom, não o calendário — não formam um conjunto de horizontes. Esta tool cobre
|
|
319
|
+
* tudo o que o Focus publica, e cada bloco diz em `tool` quem o consome.
|
|
320
|
+
*
|
|
321
|
+
* O desenho consulta os próprios recursos de expectativa — e não o recurso
|
|
322
|
+
* `DatasReferencia`, que o nome sugere e que foi descartado com o spike contra a
|
|
323
|
+
* origem: ele publica `Indicador`, `periodo`, `DataReferencia1` e
|
|
324
|
+
* `DataReferencia2` (não existe `DataReferencia`), cobre 11 indicadores contra os
|
|
325
|
+
* 26 do recurso anual, não separa por escopo e, para o IPCA, para em 12/2026
|
|
326
|
+
* enquanto as expectativas mensais já carregam referência 07/2028. É um calendário
|
|
327
|
+
* de datas de referência, não um índice das referências consultáveis.
|
|
328
|
+
*
|
|
329
|
+
* A quebra POR ESCOPO é o ponto: o conjunto de indicadores varia muito entre eles
|
|
330
|
+
* (9 no mensal contra 26 no anual), e pedir um indicador no horizonte errado —
|
|
331
|
+
* "PIB Total" no mensal, por exemplo — é justamente o modo mais comum de a
|
|
332
|
+
* consulta voltar vazia sem explicação.
|
|
333
|
+
*/
|
|
334
|
+
export async function handleFocusReferencias(args, timeoutMs, maxRetries) {
|
|
335
|
+
if (args.escopo !== undefined && !ESCOPOS.includes(args.escopo)) {
|
|
336
|
+
return erroResult(`Escopo inválido: "${args.escopo}". Use um destes: ${ESCOPOS.join(", ")}.`);
|
|
337
|
+
}
|
|
338
|
+
const escopos = args.escopo !== undefined ? [args.escopo] : ESCOPOS;
|
|
339
|
+
const dataFinal = hojeIso();
|
|
340
|
+
const dataInicial = somarDiasIso(dataFinal, -JANELA_REFERENCIAS_DIAS);
|
|
341
|
+
const consultas = escopos.map(escopo => {
|
|
342
|
+
const plano = planoDoEscopo(escopo);
|
|
343
|
+
// Filtro por construção, como em toda tool desta camada; `$select` corta o
|
|
344
|
+
// payload em ~4x (medido), porque aqui só interessam dois campos.
|
|
345
|
+
const filtro = [`Data ge ${odataString(dataInicial)}`, `Data le ${odataString(dataFinal)}`];
|
|
346
|
+
if (args.indicador !== undefined)
|
|
347
|
+
filtro.push(`Indicador eq ${odataString(args.indicador)}`);
|
|
348
|
+
const url = montarUrlOData(EXPECTATIVAS_ODATA, {
|
|
349
|
+
recurso: plano.recurso,
|
|
350
|
+
select: plano.campoReferencia ? ["Indicador", plano.campoReferencia] : ["Indicador"],
|
|
351
|
+
filtro,
|
|
352
|
+
top: OLINDA_MAX_LINHAS
|
|
353
|
+
});
|
|
354
|
+
return { plano, url };
|
|
355
|
+
});
|
|
356
|
+
const respostas = await Promise.allSettled(consultas.map(c => consultarOData(c.url, timeoutMs, maxRetries)));
|
|
357
|
+
const indicadoresUniao = new Set();
|
|
358
|
+
const referenciasUniao = new Set();
|
|
359
|
+
const falhas = [];
|
|
360
|
+
let totalRegistros = 0;
|
|
361
|
+
const blocos = consultas.map(({ plano, url }, i) => {
|
|
362
|
+
const resposta = respostas[i];
|
|
363
|
+
const linhas = resposta.status === "fulfilled" ? resposta.value : [];
|
|
364
|
+
if (resposta.status === "rejected") {
|
|
365
|
+
falhas.push({ escopo: plano.escopo, erro: mensagemDeErro(resposta.reason) });
|
|
366
|
+
}
|
|
367
|
+
const indicadores = [
|
|
368
|
+
...new Set(linhas.map(l => textoOuNulo(l.Indicador ?? l.indicador)).filter((v) => v !== null))
|
|
369
|
+
].sort();
|
|
370
|
+
const referencias = plano.campoReferencia
|
|
371
|
+
? ordenarReferencias([
|
|
372
|
+
...new Set(linhas
|
|
373
|
+
.map(l => textoOuNulo(l[plano.campoReferencia] ?? l[plano.campoReferencia.toLowerCase()]))
|
|
374
|
+
.filter((v) => v !== null))
|
|
375
|
+
])
|
|
376
|
+
: [];
|
|
377
|
+
indicadores.forEach(v => indicadoresUniao.add(v));
|
|
378
|
+
referencias.forEach(v => referenciasUniao.add(v));
|
|
379
|
+
totalRegistros += linhas.length;
|
|
380
|
+
return {
|
|
381
|
+
escopo: plano.escopo,
|
|
382
|
+
tool: plano.tool,
|
|
383
|
+
formatoReferencia: plano.formatoReferencia,
|
|
384
|
+
exigeReferencia: plano.campoReferencia !== null && plano.escopo !== "selic",
|
|
385
|
+
temTop5: plano.temTop5,
|
|
386
|
+
indicadores,
|
|
387
|
+
referencias,
|
|
388
|
+
urlConsulta: url,
|
|
389
|
+
disponivel: resposta.status === "fulfilled"
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
// Só é erro se NADA respondeu: com a origem instável, meia resposta ainda é útil.
|
|
393
|
+
if (falhas.length === escopos.length) {
|
|
394
|
+
return erroResult(`Erro ao consultar referências do Focus: ${falhas[0].erro}`);
|
|
395
|
+
}
|
|
396
|
+
const payload = {
|
|
397
|
+
filtro: { indicador: args.indicador ?? null, escopo: args.escopo ?? null },
|
|
398
|
+
janela: { dataInicial, dataFinal },
|
|
399
|
+
indicadores: [...indicadoresUniao].sort(),
|
|
400
|
+
referencias: ordenarReferencias([...referenciasUniao]),
|
|
401
|
+
escopos: blocos,
|
|
402
|
+
totalRegistros,
|
|
403
|
+
consultadoEm: new Date().toISOString(),
|
|
404
|
+
observacao: "Os nomes de indicador e as referências são sensíveis ao texto EXATO: copie daqui para " +
|
|
405
|
+
"bcb_focus_expectativas (ou para bcb_focus_selic, no caso das reuniões do Copom) — o campo `tool` de cada " +
|
|
406
|
+
"escopo diz qual. Repare que o conjunto de indicadores muda por escopo: pedir um indicador no horizonte " +
|
|
407
|
+
"em que a fonte não o publica é a causa mais comum de resposta vazia. As listas refletem as coletas dos " +
|
|
408
|
+
`últimos ${JANELA_REFERENCIAS_DIAS} dias.`
|
|
409
|
+
};
|
|
410
|
+
if (falhas.length > 0) {
|
|
411
|
+
payload.falhas = falhas;
|
|
412
|
+
payload.observacaoFalhas =
|
|
413
|
+
`${falhas.length} de ${escopos.length} escopos não responderam nesta consulta; os demais estão completos. ` +
|
|
414
|
+
"Escopos com `disponivel: false` têm listas vazias por indisponibilidade, não por ausência de dado.";
|
|
415
|
+
}
|
|
416
|
+
if (args.indicador !== undefined && indicadoresUniao.size === 0 && falhas.length === 0) {
|
|
417
|
+
payload.observacaoFalhas =
|
|
418
|
+
`Nenhum escopo publica o indicador "${args.indicador}" nos últimos ${JANELA_REFERENCIAS_DIAS} dias. ` +
|
|
419
|
+
"Chame esta tool SEM `indicador` para ver a lista de nomes publicados por escopo.";
|
|
420
|
+
}
|
|
421
|
+
return structuredResult(payload);
|
|
422
|
+
}
|
|
423
|
+
// ==================== SCHEMAS ====================
|
|
424
|
+
/**
|
|
425
|
+
* Todo campo aqui é anulável de propósito, e o schema precisa dizer isso: os
|
|
426
|
+
* treze recursos da fonte não publicam o mesmo conjunto de campos (o Top 5 não
|
|
427
|
+
* traz `numeroRespondentes` nem `baseCalculo`, os rolantes não têm referência), e
|
|
428
|
+
* a normalização devolve `null` em vez de omitir — nulo é a informação de que a
|
|
429
|
+
* fonte não publica aquilo ali. Anunciar `type: "string"` e servir `null` quebra
|
|
430
|
+
* cliente que valida `structuredContent` contra o `outputSchema`, como a spec
|
|
431
|
+
* manda e como o Inspector faz.
|
|
432
|
+
*/
|
|
433
|
+
const ESTATISTICAS_PROPS = {
|
|
434
|
+
indicador: { type: ["string", "null"], description: "Indicador conforme publicado pela fonte" },
|
|
435
|
+
indicadorDetalhe: { type: ["string", "null"], description: "Detalhe do indicador, quando a fonte publica" },
|
|
436
|
+
coletadoEm: { type: ["string", "null"], description: "Data da coleta das expectativas (yyyy-MM-dd)" },
|
|
437
|
+
referencia: { type: ["string", "null"], description: "Alvo da expectativa (data de referência ou reunião do Copom); nulo nos horizontes rolantes" },
|
|
438
|
+
media: { type: ["number", "null"], description: "Média das expectativas" },
|
|
439
|
+
mediana: { type: ["number", "null"], description: "Mediana das expectativas" },
|
|
440
|
+
desvioPadrao: { type: ["number", "null"], description: "Desvio padrão das expectativas" },
|
|
441
|
+
minimo: { type: ["number", "null"], description: "Menor expectativa informada" },
|
|
442
|
+
maximo: { type: ["number", "null"], description: "Maior expectativa informada" },
|
|
443
|
+
respondentes: { type: ["number", "null"], description: "Número de instituições respondentes; nulo no Top 5, que a fonte não acompanha deste campo" },
|
|
444
|
+
baseCalculo: { type: ["number", "null"], description: "Base de cálculo usada pela fonte; nulo no Top 5" }
|
|
445
|
+
};
|
|
446
|
+
const EXPECTATIVA_ITEM_SCHEMA = {
|
|
447
|
+
type: "object",
|
|
448
|
+
properties: {
|
|
449
|
+
...ESTATISTICAS_PROPS,
|
|
450
|
+
suavizada: { type: ["boolean", "null"], description: "Só nos horizontes rolantes: indica a série suavizada" },
|
|
451
|
+
tipoCalculo: { type: ["string", "null"], description: "Só no Top 5: tipo de cálculo publicado pela fonte" },
|
|
452
|
+
coeficienteVariacao: { type: ["number", "null"], description: "Só no Top 5 da Selic: único recurso da fonte que publica este campo" }
|
|
453
|
+
},
|
|
454
|
+
required: ["indicador", "coletadoEm", "mediana"]
|
|
455
|
+
};
|
|
456
|
+
const NOTA_FOCUS = "Fonte: Expectativas de Mercado (Focus) do Banco Central do Brasil, via Olinda OData. O Focus é vintage por " +
|
|
457
|
+
"construção: `coletadoEm` é a data da coleta e `referencia` é o alvo da expectativa — a mesma referência " +
|
|
458
|
+
"aparece em muitas coletas, e é isso que permite ver a expectativa mudar no tempo. A contagem é feita do " +
|
|
459
|
+
"nosso lado porque a fonte ignora `$count`; e o filtro é obrigatório por construção porque consulta sem " +
|
|
460
|
+
"filtro não completa na origem. Microdados por instituição NÃO são expostos: a fonte desativou esse recurso " +
|
|
461
|
+
"por risco de quebra de confidencialidade.";
|
|
462
|
+
export const FOCUS_TOOL_DEFINITIONS = [
|
|
463
|
+
{
|
|
464
|
+
name: "bcb_focus_expectativas",
|
|
465
|
+
description: "Consulta as expectativas de mercado do boletim Focus para UM indicador, com o horizonte como parâmetro: " +
|
|
466
|
+
"mensal, trimestral, anual, inflação nos próximos 12 meses e nos próximos 24 meses. Devolve média, " +
|
|
467
|
+
"mediana, desvio padrão, mínimo, máximo e número de respondentes por data de coleta. " +
|
|
468
|
+
"Quando usar: para expectativa de IPCA, IGP-M, PIB, câmbio e afins em um mês, trimestre ou ano " +
|
|
469
|
+
"específico, ou para a inflação rolante. Quando NÃO usar: para expectativa de Selic por reunião do Copom " +
|
|
470
|
+
"use bcb_focus_selic; para o valor REALIZADO (não esperado) use bcb_serie_valores. " +
|
|
471
|
+
"Regras do contrato: `referencia` é obrigatória nos horizontes de calendário (mensal, trimestral, anual) " +
|
|
472
|
+
"e recusada nos rolantes; `suavizada` só vale nos rolantes; `top5: true` traz as expectativas das cinco " +
|
|
473
|
+
"instituições mais assertivas e existe nos cinco horizontes. Se não souber o texto exato do indicador ou " +
|
|
474
|
+
"da referência, chame bcb_focus_referencias primeiro — o conjunto de indicadores MUDA por horizonte, e " +
|
|
475
|
+
"pedir um indicador no horizonte em que a fonte não o publica é a causa mais comum de resposta vazia. " +
|
|
476
|
+
"Retorna: `indicador`, `horizonte`, `base` (consenso|top5), `filtro` (referencia, dataInicial, dataFinal, " +
|
|
477
|
+
"janelaPadrao, suavizada), `totalRegistros`, `expectativas` (array normalizado), `urlConsulta`, " +
|
|
478
|
+
"`consultadoEm` e, quando aplicável, `observacao`. Sem datas, a janela padrão é de 30 dias. " +
|
|
479
|
+
NOTA_FOCUS,
|
|
480
|
+
annotations: leituraRemota("Expectativas de mercado (Focus)"),
|
|
481
|
+
inputSchema: {
|
|
482
|
+
type: "object",
|
|
483
|
+
properties: {
|
|
484
|
+
indicador: {
|
|
485
|
+
type: "string",
|
|
486
|
+
description: "Indicador exatamente como a fonte publica (ex.: 'IPCA', 'IGP-M', 'PIB Total', 'Câmbio'). Veja bcb_focus_referencias.",
|
|
487
|
+
minLength: 2
|
|
488
|
+
},
|
|
489
|
+
horizonte: {
|
|
490
|
+
type: "string",
|
|
491
|
+
enum: HORIZONTES,
|
|
492
|
+
description: "mensal, trimestral e anual usam `referencia`; inflacao_12m e inflacao_24m são rolantes e não usam"
|
|
493
|
+
},
|
|
494
|
+
referencia: {
|
|
495
|
+
type: "string",
|
|
496
|
+
description: "Alvo da expectativa: MM/yyyy (mensal), T/yyyy (trimestral) ou yyyy (anual). Obrigatória nesses três; proibida nos rolantes."
|
|
497
|
+
},
|
|
498
|
+
dataInicial: { type: "string", description: "Início da janela de COLETA (yyyy-MM-dd ou dd/MM/yyyy). Padrão: 30 dias antes do fim." },
|
|
499
|
+
dataFinal: { type: "string", description: "Fim da janela de COLETA (yyyy-MM-dd ou dd/MM/yyyy). Padrão: hoje." },
|
|
500
|
+
top5: { type: "boolean", description: "Expectativas do Top 5 (as cinco instituições mais assertivas) em vez do consenso; existe nos cinco horizontes", default: false },
|
|
501
|
+
suavizada: { type: "boolean", description: "Só nos horizontes rolantes: série suavizada (true) ou não suavizada (false)" },
|
|
502
|
+
limite: { type: "number", description: "Máximo de coletas a devolver (1-500, padrão 50)", default: 50, minimum: 1, maximum: 500 }
|
|
503
|
+
},
|
|
504
|
+
required: ["indicador", "horizonte"]
|
|
505
|
+
},
|
|
506
|
+
outputSchema: {
|
|
507
|
+
type: "object",
|
|
508
|
+
properties: {
|
|
509
|
+
indicador: { type: "string" },
|
|
510
|
+
horizonte: { type: "string", enum: HORIZONTES },
|
|
511
|
+
base: { type: "string", enum: ["consenso", "top5"] },
|
|
512
|
+
filtro: {
|
|
513
|
+
type: "object",
|
|
514
|
+
description: "Filtro efetivamente aplicado na origem; nulo onde o parâmetro não foi informado",
|
|
515
|
+
properties: {
|
|
516
|
+
referencia: { type: ["string", "null"] },
|
|
517
|
+
dataInicial: { type: "string" },
|
|
518
|
+
dataFinal: { type: "string" },
|
|
519
|
+
janelaPadrao: { type: "boolean", description: "true quando a janela de 30 dias foi assumida" },
|
|
520
|
+
suavizada: { type: ["boolean", "null"] }
|
|
521
|
+
},
|
|
522
|
+
required: ["dataInicial", "dataFinal", "janelaPadrao"]
|
|
523
|
+
},
|
|
524
|
+
totalRegistros: { type: "number", description: "Coletas encontradas (contagem client-side)" },
|
|
525
|
+
expectativas: { type: "array", items: EXPECTATIVA_ITEM_SCHEMA },
|
|
526
|
+
urlConsulta: { type: "string", description: "URL OData consultada, reproduzível no navegador" },
|
|
527
|
+
consultadoEm: { type: "string", description: "Timestamp ISO 8601 da consulta" },
|
|
528
|
+
observacao: { type: "string" }
|
|
529
|
+
},
|
|
530
|
+
required: ["indicador", "horizonte", "base", "filtro", "totalRegistros", "expectativas", "urlConsulta", "consultadoEm"]
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "bcb_focus_selic",
|
|
535
|
+
description: "Consulta as expectativas de mercado do Focus para a taxa Selic, organizadas pela REUNIÃO do Copom " +
|
|
536
|
+
"(formato R1/2026 = 1ª reunião de 2026). Devolve média, mediana, desvio padrão, mínimo, máximo e número " +
|
|
537
|
+
"de respondentes por data de coleta. " +
|
|
538
|
+
"Quando usar: para 'o que o mercado espera da Selic na próxima reunião' ou a trajetória esperada de " +
|
|
539
|
+
"juros. Quando NÃO usar: para expectativa de Selic média de um ano civil use bcb_focus_expectativas com " +
|
|
540
|
+
"horizonte anual; para a Selic REALIZADA use bcb_serie_valores (códigos 432, 1178, 4390). " +
|
|
541
|
+
"É separada de bcb_focus_expectativas porque o eixo temporal é a reunião do Copom, não o calendário. " +
|
|
542
|
+
"Retorna: `base` (consenso|top5), `filtro`, `totalRegistros`, `expectativas` (com `referencia` = reunião), " +
|
|
543
|
+
"`urlConsulta`, `consultadoEm` e `observacaoEixo`. Sem datas, a janela padrão é de 30 dias. " +
|
|
544
|
+
NOTA_FOCUS,
|
|
545
|
+
annotations: leituraRemota("Expectativas de Selic (Focus)"),
|
|
546
|
+
inputSchema: {
|
|
547
|
+
type: "object",
|
|
548
|
+
properties: {
|
|
549
|
+
reuniao: { type: "string", description: "Reunião do Copom no formato R1/2026 (opcional; sem ela, todas as reuniões da janela)" },
|
|
550
|
+
dataInicial: { type: "string", description: "Início da janela de COLETA (yyyy-MM-dd ou dd/MM/yyyy). Padrão: 30 dias antes do fim." },
|
|
551
|
+
dataFinal: { type: "string", description: "Fim da janela de COLETA (yyyy-MM-dd ou dd/MM/yyyy). Padrão: hoje." },
|
|
552
|
+
top5: { type: "boolean", description: "Expectativas do Top 5 em vez do consenso", default: false },
|
|
553
|
+
limite: { type: "number", description: "Máximo de coletas a devolver (1-500, padrão 50)", default: 50, minimum: 1, maximum: 500 }
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
outputSchema: {
|
|
557
|
+
type: "object",
|
|
558
|
+
properties: {
|
|
559
|
+
base: { type: "string", enum: ["consenso", "top5"] },
|
|
560
|
+
filtro: {
|
|
561
|
+
type: "object",
|
|
562
|
+
description: "Filtro efetivamente aplicado; `reuniao` é nula quando não foi informada",
|
|
563
|
+
properties: {
|
|
564
|
+
reuniao: { type: ["string", "null"] },
|
|
565
|
+
dataInicial: { type: "string" },
|
|
566
|
+
dataFinal: { type: "string" },
|
|
567
|
+
janelaPadrao: { type: "boolean" }
|
|
568
|
+
},
|
|
569
|
+
required: ["dataInicial", "dataFinal", "janelaPadrao"]
|
|
570
|
+
},
|
|
571
|
+
totalRegistros: { type: "number" },
|
|
572
|
+
expectativas: { type: "array", items: EXPECTATIVA_ITEM_SCHEMA },
|
|
573
|
+
urlConsulta: { type: "string" },
|
|
574
|
+
consultadoEm: { type: "string" },
|
|
575
|
+
observacaoEixo: { type: "string" },
|
|
576
|
+
observacao: { type: "string" }
|
|
577
|
+
},
|
|
578
|
+
required: ["base", "filtro", "totalRegistros", "expectativas", "urlConsulta", "consultadoEm"]
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: "bcb_focus_referencias",
|
|
583
|
+
description: "Lista, POR ESCOPO, os indicadores e as referências que o Focus efetivamente publica, para você usar o " +
|
|
584
|
+
"texto EXATO em bcb_focus_expectativas e em bcb_focus_selic. Escopo = os cinco horizontes de " +
|
|
585
|
+
"bcb_focus_expectativas mais 'selic', que não é horizonte: o eixo dela é a reunião do Copom, e quem a " +
|
|
586
|
+
"consome é bcb_focus_selic. Cada bloco diz em `tool` quem o consome. " +
|
|
587
|
+
"Quando usar: antes da primeira consulta ao Focus, ou quando uma consulta volta vazia — a causa mais " +
|
|
588
|
+
"comum não é o dado faltar, é o indicador não existir NAQUELE escopo (a fonte publica 9 indicadores no " +
|
|
589
|
+
"mensal e 26 no anual: 'PIB Total', por exemplo, não existe no mensal) ou a referência estar num formato " +
|
|
590
|
+
"diferente do publicado. Quando NÃO usar: para os valores das expectativas em si. " +
|
|
591
|
+
"Sem `escopo`, consulta os seis e devolve tudo; com `escopo`, consulta só aquele. " +
|
|
592
|
+
"Retorna: `escopos` (para cada um: `tool` que o consome, `formatoReferencia`, `exigeReferencia`, " +
|
|
593
|
+
"`temTop5`, `indicadores`, `referencias`, `urlConsulta` e `disponivel`), mais `indicadores` e " +
|
|
594
|
+
"`referencias` como união de todos, `janela`, `totalRegistros` e `consultadoEm`. Se algum escopo não " +
|
|
595
|
+
"responder, os demais voltam mesmo assim, com `falhas` preenchido. " + NOTA_FOCUS,
|
|
596
|
+
annotations: leituraRemota("Indicadores e referências do Focus"),
|
|
597
|
+
inputSchema: {
|
|
598
|
+
type: "object",
|
|
599
|
+
properties: {
|
|
600
|
+
indicador: { type: "string", description: "Filtrar por um indicador específico, para ver em quais escopos ele existe (opcional)" },
|
|
601
|
+
escopo: {
|
|
602
|
+
type: "string",
|
|
603
|
+
enum: ESCOPOS,
|
|
604
|
+
description: "Restringe a descoberta a um escopo (opcional). 'selic' descobre as reuniões do Copom para bcb_focus_selic; os demais são os horizontes de bcb_focus_expectativas."
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
outputSchema: {
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: {
|
|
611
|
+
filtro: {
|
|
612
|
+
type: "object",
|
|
613
|
+
description: "Filtro pedido; nulo onde o parâmetro não foi informado",
|
|
614
|
+
properties: {
|
|
615
|
+
indicador: { type: ["string", "null"] },
|
|
616
|
+
escopo: { type: ["string", "null"], description: `Um de: ${ESCOPOS.join(", ")}; nulo quando a consulta cobriu todos` }
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
janela: {
|
|
620
|
+
type: "object",
|
|
621
|
+
description: "Janela de coleta observada para montar as listas",
|
|
622
|
+
properties: {
|
|
623
|
+
dataInicial: { type: "string" },
|
|
624
|
+
dataFinal: { type: "string" }
|
|
625
|
+
},
|
|
626
|
+
required: ["dataInicial", "dataFinal"]
|
|
627
|
+
},
|
|
628
|
+
indicadores: { type: "array", items: { type: "string" }, description: "União dos indicadores de todos os escopos consultados" },
|
|
629
|
+
referencias: { type: "array", items: { type: "string" }, description: "União das referências de todos os escopos consultados" },
|
|
630
|
+
escopos: {
|
|
631
|
+
type: "array",
|
|
632
|
+
description: "Um bloco por escopo: regras do contrato mais o que a fonte publica nele",
|
|
633
|
+
items: {
|
|
634
|
+
type: "object",
|
|
635
|
+
properties: {
|
|
636
|
+
escopo: { type: "string", enum: ESCOPOS },
|
|
637
|
+
tool: { type: "string", description: "Tool que consome este escopo" },
|
|
638
|
+
formatoReferencia: { type: ["string", "null"], description: "Formato da referência; nulo nos horizontes rolantes, que não têm alvo de calendário" },
|
|
639
|
+
exigeReferencia: { type: "boolean", description: "true nos horizontes de calendário, onde `referencia` é obrigatória" },
|
|
640
|
+
temTop5: { type: "boolean" },
|
|
641
|
+
indicadores: { type: "array", items: { type: "string" }, description: "Indicadores publicados NESTE escopo" },
|
|
642
|
+
referencias: { type: "array", items: { type: "string" }, description: "Referências publicadas NESTE escopo; vazio nos rolantes, que não têm alvo de calendário" },
|
|
643
|
+
urlConsulta: { type: "string" },
|
|
644
|
+
disponivel: { type: "boolean", description: "false quando a origem não respondeu por este escopo — listas vazias por indisponibilidade, não por ausência de dado" }
|
|
645
|
+
},
|
|
646
|
+
required: ["escopo", "tool", "exigeReferencia", "temTop5", "indicadores", "referencias", "urlConsulta", "disponivel"]
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
totalRegistros: { type: "number" },
|
|
650
|
+
falhas: {
|
|
651
|
+
type: "array",
|
|
652
|
+
description: "Escopos que não responderam nesta consulta",
|
|
653
|
+
items: {
|
|
654
|
+
type: "object",
|
|
655
|
+
properties: {
|
|
656
|
+
escopo: { type: "string" },
|
|
657
|
+
erro: { type: "string" }
|
|
658
|
+
},
|
|
659
|
+
required: ["escopo", "erro"]
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
consultadoEm: { type: "string" },
|
|
663
|
+
observacao: { type: "string" },
|
|
664
|
+
observacaoFalhas: { type: "string" }
|
|
665
|
+
},
|
|
666
|
+
required: ["indicadores", "referencias", "escopos", "janela", "totalRegistros", "consultadoEm"]
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
];
|
|
670
|
+
/** Retorna null quando a tool não é deste módulo (o dispatcher central segue). */
|
|
671
|
+
export function dispatchFocusTool(toolName, args, timeoutMs, maxRetries) {
|
|
672
|
+
switch (toolName) {
|
|
673
|
+
case "bcb_focus_expectativas":
|
|
674
|
+
return handleFocusExpectativas(args, timeoutMs, maxRetries);
|
|
675
|
+
case "bcb_focus_selic":
|
|
676
|
+
return handleFocusSelic(args, timeoutMs, maxRetries);
|
|
677
|
+
case "bcb_focus_referencias":
|
|
678
|
+
return handleFocusReferencias(args, timeoutMs, maxRetries);
|
|
679
|
+
default:
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
//# sourceMappingURL=focus.js.map
|