sih-br-mcp 0.12.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sidney Bissoli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # sih-br-mcp
2
+
3
+ Servidor MCP (Model Context Protocol) para análise das internações hospitalares do
4
+ SUS (SIH/SUS, AIH reduzida) com foco em ICSAP — internações por condições sensíveis
5
+ à atenção primária. Doze ferramentas sobre cubos anuais de 1992 a 2025 (causas por
6
+ capítulo/grupo CID, séries mensais, ICSAP por município, taxas por 100 mil), com a
7
+ proveniência da safra em cada resposta.
8
+
9
+ ## De onde vêm os dados
10
+
11
+ Este servidor é **consumidor** do canal público `sih/cubos/` do projeto
12
+ [healthbr-data](https://github.com/SidneyBissoli/healthbr-data):
13
+
14
+ ```
15
+ Ministério da Saúde / DATASUS (RD<UF><AAMM>.dbc, FTP)
16
+ → healthbr-data sih/rd/ (Parquet 1:1, manifesto com MD5 e data de download)
17
+ → healthbr-data pipeline sih-cubos (scripts/pipeline/sih-cubos/build-aggregations.R)
18
+ → https://data.sidneybissoli.com/sih/cubos/ (cubos + sidecar por ano + manifest.json + tables/)
19
+ → este servidor (cache local sob demanda, SHA-256 conferido contra o manifesto)
20
+ ```
21
+
22
+ Até 08/09/2026 o builder dos cubos vivia aqui (`scripts/build-aggregations.R`,
23
+ `rebuild-cubes.yml`); desde então o produtor é o healthbr-data e este repositório
24
+ não gera nem publica cubo nenhum (CONTEXT.md, decisão 27). A receita completa está
25
+ em `healthbr-data/scripts/pipeline/sih-cubos/README.md` e no card
26
+ [`sih-cubos`](https://github.com/SidneyBissoli/healthbr-data/blob/master/guides/dataset-cards/sih-cubos-README.md).
27
+
28
+ - **Cubos:** baixados por ano, só os que a chamada pede, para
29
+ `~/.cache/sih-br-mcp/cubos/` (`SIH_CACHE_DIR`), com o sidecar
30
+ `sih_provenance_<ano>.json` ao lado. `SIH_CUBES_BASE_URL` aponta outro canal;
31
+ `SIH_CUBES_CACHE=off` desliga (smoke e golden usam).
32
+ - **Frescor:** `src/freshness.ts` compara o sidecar com `sih/rd/manifest-summary.json`
33
+ e avisa quando um cubo está atrás do espelho; quem reconstrói é o produtor
34
+ (`rebuild-sih-cubes.yml`, toda terça e após cada manutenção do espelho).
35
+ - **Tabelas de classificação** (`src/data/`): cópias do contrato publicado em
36
+ `sih/cubos/tables/`; `npm run tables:check` confere o SHA-256 contra o manifesto
37
+ (roda no CI). Nunca edite aqui — a fonte é o produtor.
38
+ - **População** (`pop_uf.parquet`, `pop_uf_agregado.parquet`, `pop_municipios.parquet`):
39
+ desde a 0.12.0 vem do mesmo canal, assinada no bloco `population` do `manifest.json`
40
+ (produtor: `build-population.R` + `build-sih-population.yml` do healthbr-data — IBGE,
41
+ Projeção 2024 por UF; DATASUS POPBR/POPSVS por município). As ferramentas de taxa
42
+ (`get_hospitalization_rates`, `compare_icsap_trends` com `rate_per_10k`) e
43
+ `get_available_years` baixam os três arquivos para o cache na primeira chamada,
44
+ com SHA-256 conferido; uma pasta de dados que já tenha `pop_uf.parquet` tem
45
+ precedência (fixture, build local). A proveniência da população responde com o
46
+ `built_at` do manifesto.
47
+
48
+ ## Uso
49
+
50
+ Pacote no npm: [`sih-br-mcp`](https://www.npmjs.com/package/sih-br-mcp) (Node 22+).
51
+ Ele não embarca dado nenhum — cubos, tabelas e população vêm do canal na primeira
52
+ chamada e ficam no cache local.
53
+
54
+ ```bash
55
+ npx -y sih-br-mcp # stdio
56
+ ```
57
+
58
+ Configuração num cliente MCP (Claude Desktop, Claude Code):
59
+
60
+ ```json
61
+ { "mcpServers": { "sih": { "command": "npx", "args": ["-y", "sih-br-mcp"] } } }
62
+ ```
63
+
64
+ A partir do código-fonte:
65
+
66
+ ```bash
67
+ npm install
68
+ npm run build
69
+ node dist/index.js # stdio
70
+ ```
71
+
72
+ Variáveis: `SIH_DATA_DIR` (pasta com cubos já prontos, em vez do cache),
73
+ `SIH_CACHE_DIR`, `SIH_CUBES_BASE_URL`, `SIH_CUBES_CACHE=off`,
74
+ `SIH_FRESHNESS_CHECK=off`.
75
+
76
+ ## Verificação
77
+
78
+ ```bash
79
+ npm ci && npm run build
80
+ npm run smoke:stdio # superfície das ferramentas × baselines/surface-stdio.json
81
+ npm run golden:tools # 12 ferramentas byte a byte × baselines/golden-tools.json (fixture 2023/RR)
82
+ npm run freshness:selftest # frescor offline sobre um trecho versionado do manifesto
83
+ npm run cache:selftest # cache local (download + SHA-256) contra um canal falso
84
+ npm run tables:check # tabelas de src/data × manifesto do canal
85
+ ```
86
+
87
+ O CI (`.github/workflows/ci.yml`) roda tudo isso em Node 22 e 24. A fixture
88
+ `tests/fixtures/sih/` é uma cópia real dos cubos de 2023/RR gerados pelo builder
89
+ (hoje no healthbr-data) — é o que torna medível qualquer bump.
90
+
91
+ ## Documentação
92
+
93
+ - `CONTEXT.md` — decisões arquiteturais numeradas (a 27 é a migração do produtor).
94
+ - `docs/analise-001` (janela de competências), `analise-002` (era CID-9, 1992–1997),
95
+ `analise-003` (lista ICSAP em CID-9 derivada), `plan-002` (DuckDB Node Neo),
96
+ `plan-003` (rebuild automático, hoje no healthbr-data), `tool-specifications.md`.
97
+
98
+ ## Licença
99
+
100
+ MIT (`LICENSE`). Os dados são do Ministério da Saúde / DATASUS; a redistribuição em
101
+ Parquet e os cubos derivados são do healthbr-data (CC-BY-4.0).
@@ -0,0 +1,108 @@
1
+ export declare const CUBES_BASE_URL: string;
2
+ export declare const CUBES_CACHE_ENABLED: boolean;
3
+ export type CubeKind = "causas" | "series" | "icsap";
4
+ export interface CubesManifestFile {
5
+ name: string;
6
+ size_bytes: number;
7
+ sha256: string;
8
+ }
9
+ export interface CubesManifestYear {
10
+ built_at: string | null;
11
+ builder_version: string | null;
12
+ records_in_cube: number | null;
13
+ window_complete: boolean | null;
14
+ ufs: number | null;
15
+ manifest_last_updated: string | null;
16
+ files: Record<CubeKind | "provenance", CubesManifestFile>;
17
+ }
18
+ export type PopulationKind = "pop_uf" | "pop_uf_agregado" | "pop_municipios";
19
+ /**
20
+ * >= 1.2.0 (2026-09-08, sih:populacao-no-canal): os denominadores populacionais
21
+ * publicados ao lado dos cubos pelo build-sih-population.yml do healthbr-data
22
+ * (build-population.R): pop_uf (IBGE, Projeção 2024, UF × sexo × idade simples,
23
+ * 2000..último cubo FECHADO), pop_municipios (DATASUS POPBR/POPSVS 1991–2024),
24
+ * pop_uf_agregado (soma por UF, 1991–1999) e o sidecar pop_provenance.json.
25
+ */
26
+ export interface CubesManifestPopulation {
27
+ built_at: string | null;
28
+ builder_version: string | null;
29
+ /** Último ano de pop_uf = último cubo FECHADO do SIH (regra do CONTEXT.md). */
30
+ last_year: number | null;
31
+ rule?: string | null;
32
+ sources?: Array<{
33
+ file: string;
34
+ name: string;
35
+ agency?: string | null;
36
+ url?: string | null;
37
+ years?: string | null;
38
+ }>;
39
+ files: Record<PopulationKind | "provenance", CubesManifestFile>;
40
+ }
41
+ export interface CubesManifest {
42
+ manifest_version: string;
43
+ dataset: string;
44
+ generated_at: string;
45
+ base_url: string;
46
+ /** >= 1.1.0 (2026-09-08): quem gerou — repositório e pipeline do produtor. */
47
+ producer?: {
48
+ repository?: string;
49
+ pipeline?: string;
50
+ workflow?: string | null;
51
+ run_url?: string | null;
52
+ };
53
+ /** >= 1.1.0: tabelas de classificação publicadas em tables/, com SHA-256 — src/data/ é cópia (scripts/tables-check.mjs). */
54
+ tables?: Record<string, CubesManifestFile>;
55
+ /** >= 1.2.0: denominadores populacionais; null/ausente em manifesto anterior à população (não é erro do canal). */
56
+ population?: CubesManifestPopulation | null;
57
+ years: Record<string, CubesManifestYear>;
58
+ }
59
+ export declare function cubesCacheDir(): string;
60
+ /** Anos com os três cubos presentes numa pasta. */
61
+ export declare function yearsPresent(dir: string): number[];
62
+ /** Os três arquivos de população presentes numa pasta (o sidecar é opcional). */
63
+ export declare function populationPresent(dir: string): boolean;
64
+ /**
65
+ * Manifesto do canal: remoto (timeout curto), senão a cópia em disco da última
66
+ * vez, senão null. Memoizado por 10 min. Nunca lança.
67
+ */
68
+ export declare function loadCubesManifest(opts?: {
69
+ timeoutMs?: number;
70
+ }): Promise<{
71
+ manifest: CubesManifest | null;
72
+ source: "remote" | "disk" | "none";
73
+ }>;
74
+ export declare function publishedYears(manifest: CubesManifest | null): number[];
75
+ /** Só para testes: injeta um manifesto e zera a memoização. */
76
+ export declare function setCubesManifestForTests(manifest: CubesManifest | null): void;
77
+ /**
78
+ * O manifesto já carregado nesta execução (memoizado), sem ir à rede — para
79
+ * quem é síncrono, como populationProvenance(): depois de ensurePopulation()
80
+ * o memo está cheio; com o cache desligado (fixture, golden) fica null.
81
+ */
82
+ export declare function cachedCubesManifest(): CubesManifest | null;
83
+ /** Um ano completo = três cubos + sidecar, todos verificados. Idempotente e sem corrida. */
84
+ export declare function ensureYear(dir: string, year: number, manifest: CubesManifest, log?: (msg: string) => void): Promise<void>;
85
+ /**
86
+ * Garante os anos pedidos na pasta; `null` = todos os anos publicados (chamada
87
+ * sem ano explícito varre a série inteira). Devolve o que baixou e o que não
88
+ * existe no canal. Falha de rede num ano lança — o handler decide a mensagem.
89
+ */
90
+ export declare function ensureYears(dir: string, years: number[] | null, log?: (msg: string) => void): Promise<{
91
+ downloaded: number[];
92
+ unavailable: number[];
93
+ }>;
94
+ /**
95
+ * Garante os denominadores populacionais (três Parquet + pop_provenance.json)
96
+ * na pasta, baixados do canal e verificados como um ano de cubos. Idempotente
97
+ * (arquivo presente com o tamanho do manifesto não baixa) e sem corrida.
98
+ * `available = false` quando o manifesto do canal não tem o bloco `population`
99
+ * (manifesto anterior a 1.2.0) — o handler responde "sem denominador", não
100
+ * "erro do canal". Falha de rede lança — o handler decide a mensagem.
101
+ */
102
+ export declare function ensurePopulation(dir: string, log?: (msg: string) => void): Promise<{
103
+ downloaded: string[];
104
+ available: boolean;
105
+ }>;
106
+ /** Anos que uma chamada de ferramenta pede, lidos dos argumentos; null = não diz. */
107
+ export declare function yearsFromArgs(args: unknown): number[] | null;
108
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
package/dist/cache.js ADDED
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Cache local dos cubos — item (c) de `sih:cubos-frescor` (2026-09-06).
3
+ *
4
+ * Os cubos anuais (`sih_{causas,series,icsap}_<ano>.parquet` + sidecar) são
5
+ * publicados pela pipeline sih-cubos do healthbr-data (rebuild-sih-cubes.yml;
6
+ * até 08/09/2026 o produtor era o rebuild-cubes.yml deste repositório) em
7
+ * `sih/cubos/` do bucket, servido em https://data.sidneybissoli.com/sih/cubos/
8
+ * com um `manifest.json` (tamanho e SHA-256 por arquivo e por tabela de
9
+ * classificação; healthbr-data scripts/pipeline/sih-cubos/cubes-manifest.mjs). O pacote npm
10
+ * NÃO embarca cubo nenhum (~60 MB por ano): quando a pasta de dados do
11
+ * projeto não tem cubos, o servidor usa `~/.cache/sih-br-mcp/cubos/` e baixa
12
+ * de lá só os anos que a chamada pede, verificando SHA-256 antes de aceitar.
13
+ *
14
+ * Variáveis de ambiente:
15
+ * SIH_CUBES_CACHE=off desliga tudo (smoke e golden usam: o baseline não
16
+ * pode depender de rede — ver docs/plan-003).
17
+ * SIH_CACHE_DIR=<pasta> onde guardar (padrão ~/.cache/sih-br-mcp/cubos).
18
+ * SIH_CUBES_BASE_URL=<url> outro canal (espelho, teste local).
19
+ *
20
+ * O que NÃO faz: não apaga nada, não atualiza cubo já presente (quem decide
21
+ * que um cubo está atrás é o frescor, `src/freshness.ts`; a reposição é
22
+ * apagar o arquivo do cache e chamar de novo).
23
+ */
24
+ import { createHash } from "node:crypto";
25
+ import { createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
26
+ import { homedir } from "node:os";
27
+ import { join, resolve } from "node:path";
28
+ import { Readable } from "node:stream";
29
+ import { pipeline } from "node:stream/promises";
30
+ export const CUBES_BASE_URL = (process.env.SIH_CUBES_BASE_URL ?? "https://data.sidneybissoli.com/sih/cubos/").replace(/\/?$/, "/");
31
+ export const CUBES_CACHE_ENABLED = process.env.SIH_CUBES_CACHE !== "off";
32
+ const CUBE_KINDS = ["causas", "series", "icsap"];
33
+ const POPULATION_KINDS = ["pop_uf", "pop_uf_agregado", "pop_municipios"];
34
+ export function cubesCacheDir() {
35
+ return process.env.SIH_CACHE_DIR ? resolve(process.env.SIH_CACHE_DIR) : join(homedir(), ".cache", "sih-br-mcp", "cubos");
36
+ }
37
+ /** Anos com os três cubos presentes numa pasta. */
38
+ export function yearsPresent(dir) {
39
+ if (!existsSync(dir))
40
+ return [];
41
+ const years = new Set();
42
+ for (let y = 1990; y <= 2100; y++) {
43
+ if (CUBE_KINDS.every((k) => existsSync(join(dir, `sih_${k}_${y}.parquet`))))
44
+ years.add(y);
45
+ }
46
+ return [...years].sort((a, b) => a - b);
47
+ }
48
+ /** Os três arquivos de população presentes numa pasta (o sidecar é opcional). */
49
+ export function populationPresent(dir) {
50
+ return existsSync(dir) && POPULATION_KINDS.every((k) => existsSync(join(dir, `${k}.parquet`)));
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Manifesto
54
+ const MANIFEST_TTL_MS = 10 * 60 * 1000;
55
+ let manifestMemo = null;
56
+ function parseManifest(text) {
57
+ try {
58
+ const m = JSON.parse(text);
59
+ if (!m || typeof m !== "object" || typeof m.years !== "object")
60
+ return null;
61
+ return m;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ /**
68
+ * Manifesto do canal: remoto (timeout curto), senão a cópia em disco da última
69
+ * vez, senão null. Memoizado por 10 min. Nunca lança.
70
+ */
71
+ export async function loadCubesManifest(opts = {}) {
72
+ if (manifestMemo && Date.now() - manifestMemo.at < MANIFEST_TTL_MS)
73
+ return manifestMemo;
74
+ const diskPath = join(cubesCacheDir(), "manifest.json");
75
+ let manifest = null;
76
+ let source = "none";
77
+ try {
78
+ const res = await fetch(CUBES_BASE_URL + "manifest.json", { signal: AbortSignal.timeout(opts.timeoutMs ?? 8000) });
79
+ if (res.ok) {
80
+ const text = await res.text();
81
+ manifest = parseManifest(text);
82
+ if (manifest) {
83
+ source = "remote";
84
+ try {
85
+ mkdirSync(cubesCacheDir(), { recursive: true });
86
+ writeFileSync(diskPath, text);
87
+ }
88
+ catch {
89
+ /* cache em disco é conveniência */
90
+ }
91
+ }
92
+ }
93
+ }
94
+ catch {
95
+ /* rede indisponível: cai para o disco */
96
+ }
97
+ if (!manifest && existsSync(diskPath)) {
98
+ manifest = parseManifest(readFileSync(diskPath, "utf8"));
99
+ if (manifest)
100
+ source = "disk";
101
+ }
102
+ manifestMemo = { at: Date.now(), manifest, source };
103
+ return manifestMemo;
104
+ }
105
+ export function publishedYears(manifest) {
106
+ if (!manifest)
107
+ return [];
108
+ return Object.keys(manifest.years).map(Number).filter((y) => Number.isFinite(y)).sort((a, b) => a - b);
109
+ }
110
+ /** Só para testes: injeta um manifesto e zera a memoização. */
111
+ export function setCubesManifestForTests(manifest) {
112
+ manifestMemo = manifest ? { at: Date.now(), manifest, source: "remote" } : null;
113
+ }
114
+ /**
115
+ * O manifesto já carregado nesta execução (memoizado), sem ir à rede — para
116
+ * quem é síncrono, como populationProvenance(): depois de ensurePopulation()
117
+ * o memo está cheio; com o cache desligado (fixture, golden) fica null.
118
+ */
119
+ export function cachedCubesManifest() {
120
+ return manifestMemo?.manifest ?? null;
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // Download com verificação
124
+ async function sha256File(path) {
125
+ const { createReadStream } = await import("node:fs");
126
+ return new Promise((res, rej) => {
127
+ const h = createHash("sha256");
128
+ createReadStream(path).on("data", (d) => h.update(d)).on("end", () => res(h.digest("hex"))).on("error", rej);
129
+ });
130
+ }
131
+ async function downloadVerified(file, dir, timeoutMs) {
132
+ // ?v=<sha256>: os objetos do canal são reescritos NO LUGAR a cada rebuild e a
133
+ // borda do domínio guarda cópia pelo Cache-Control do objeto — sem isto, um
134
+ // consumidor recebia o cubo do build anterior (HIT, Age 15 h) enquanto o
135
+ // manifesto já assinava o novo, e a verificação abaixo falhava (2026-09-08,
136
+ // sih_series_2023: 36.047 bytes servidos vs 36.352 no manifesto). A query
137
+ // entra na chave de cache: versão nova = URL nova = nunca a cópia velha.
138
+ const url = `${CUBES_BASE_URL}${file.name}?v=${file.sha256.slice(0, 16)}`;
139
+ const final = join(dir, file.name);
140
+ const tmp = `${final}.part-${process.pid}`;
141
+ const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
142
+ if (!res.ok || !res.body)
143
+ throw new Error(`${url}: HTTP ${res.status}`);
144
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(tmp));
145
+ const size = statSync(tmp).size;
146
+ if (size !== file.size_bytes) {
147
+ unlinkSync(tmp);
148
+ throw new Error(`${file.name}: ${size} bytes recebidos, manifesto diz ${file.size_bytes}`);
149
+ }
150
+ const digest = await sha256File(tmp);
151
+ if (digest !== file.sha256) {
152
+ unlinkSync(tmp);
153
+ throw new Error(`${file.name}: SHA-256 não confere com o manifesto`);
154
+ }
155
+ renameSync(tmp, final);
156
+ }
157
+ const inFlight = new Map();
158
+ /** Um ano completo = três cubos + sidecar, todos verificados. Idempotente e sem corrida. */
159
+ export function ensureYear(dir, year, manifest, log = () => { }) {
160
+ const entry = manifest.years[String(year)];
161
+ if (!entry)
162
+ return Promise.reject(new Error(`ano ${year} não está no canal ${CUBES_BASE_URL}`));
163
+ const have = inFlight.get(year);
164
+ if (have)
165
+ return have;
166
+ const p = (async () => {
167
+ mkdirSync(dir, { recursive: true });
168
+ const wanted = [...CUBE_KINDS.map((k) => entry.files[k]), entry.files.provenance].filter(Boolean);
169
+ for (const f of wanted) {
170
+ const path = join(dir, f.name);
171
+ if (existsSync(path) && statSync(path).size === f.size_bytes)
172
+ continue;
173
+ const t0 = Date.now();
174
+ log(`baixando ${f.name} (${(f.size_bytes / 1e6).toFixed(1)} MB) de ${CUBES_BASE_URL}`);
175
+ await downloadVerified(f, dir, 10 * 60 * 1000);
176
+ log(` ${f.name} ok em ${((Date.now() - t0) / 1000).toFixed(1)} s (SHA-256 confere)`);
177
+ }
178
+ })().finally(() => inFlight.delete(year));
179
+ inFlight.set(year, p);
180
+ return p;
181
+ }
182
+ /**
183
+ * Garante os anos pedidos na pasta; `null` = todos os anos publicados (chamada
184
+ * sem ano explícito varre a série inteira). Devolve o que baixou e o que não
185
+ * existe no canal. Falha de rede num ano lança — o handler decide a mensagem.
186
+ */
187
+ export async function ensureYears(dir, years, log) {
188
+ const { manifest } = await loadCubesManifest();
189
+ if (!manifest)
190
+ throw new Error(`manifesto dos cubos indisponível (${CUBES_BASE_URL}manifest.json) e sem cópia local`);
191
+ const present = new Set(yearsPresent(dir));
192
+ const wanted = (years ?? publishedYears(manifest)).filter((y) => !present.has(y));
193
+ const downloaded = [];
194
+ const unavailable = [];
195
+ for (const y of wanted) {
196
+ if (!manifest.years[String(y)]) {
197
+ unavailable.push(y);
198
+ continue;
199
+ }
200
+ await ensureYear(dir, y, manifest, log);
201
+ downloaded.push(y);
202
+ }
203
+ return { downloaded, unavailable };
204
+ }
205
+ let populationInFlight = null;
206
+ /**
207
+ * Garante os denominadores populacionais (três Parquet + pop_provenance.json)
208
+ * na pasta, baixados do canal e verificados como um ano de cubos. Idempotente
209
+ * (arquivo presente com o tamanho do manifesto não baixa) e sem corrida.
210
+ * `available = false` quando o manifesto do canal não tem o bloco `population`
211
+ * (manifesto anterior a 1.2.0) — o handler responde "sem denominador", não
212
+ * "erro do canal". Falha de rede lança — o handler decide a mensagem.
213
+ */
214
+ export function ensurePopulation(dir, log = () => { }) {
215
+ if (populationInFlight)
216
+ return populationInFlight;
217
+ const p = (async () => {
218
+ const { manifest } = await loadCubesManifest();
219
+ if (!manifest)
220
+ throw new Error(`manifesto dos cubos indisponível (${CUBES_BASE_URL}manifest.json) e sem cópia local`);
221
+ const pop = manifest.population;
222
+ if (!pop || !pop.files)
223
+ return { downloaded: [], available: false };
224
+ mkdirSync(dir, { recursive: true });
225
+ const wanted = [...POPULATION_KINDS.map((k) => pop.files[k]), pop.files.provenance].filter(Boolean);
226
+ const downloaded = [];
227
+ for (const f of wanted) {
228
+ const path = join(dir, f.name);
229
+ if (existsSync(path) && statSync(path).size === f.size_bytes)
230
+ continue;
231
+ const t0 = Date.now();
232
+ log(`baixando ${f.name} (${(f.size_bytes / 1e6).toFixed(1)} MB) de ${CUBES_BASE_URL}`);
233
+ await downloadVerified(f, dir, 10 * 60 * 1000);
234
+ log(` ${f.name} ok em ${((Date.now() - t0) / 1000).toFixed(1)} s (SHA-256 confere)`);
235
+ downloaded.push(f.name);
236
+ }
237
+ return { downloaded, available: true };
238
+ })().finally(() => {
239
+ populationInFlight = null;
240
+ });
241
+ populationInFlight = p;
242
+ return p;
243
+ }
244
+ /** Anos que uma chamada de ferramenta pede, lidos dos argumentos; null = não diz. */
245
+ export function yearsFromArgs(args) {
246
+ if (!args || typeof args !== "object")
247
+ return null;
248
+ const a = args;
249
+ const out = new Set();
250
+ const push = (v) => {
251
+ if (Array.isArray(v))
252
+ v.forEach(push);
253
+ else if (typeof v === "number" && Number.isFinite(v))
254
+ out.add(v);
255
+ else if (typeof v === "string" && /^\d{4}$/.test(v))
256
+ out.add(Number(v));
257
+ };
258
+ push(a.year);
259
+ push(a.years);
260
+ if (typeof a.start_year === "number" && typeof a.end_year === "number" && a.end_year >= a.start_year) {
261
+ for (let y = a.start_year; y <= a.end_year; y++)
262
+ out.add(y);
263
+ }
264
+ else {
265
+ push(a.start_year);
266
+ push(a.end_year);
267
+ }
268
+ return out.size ? [...out].sort((x, y) => x - y) : null;
269
+ }
270
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ {
2
+ "metadata": {
3
+ "source": "IBGE",
4
+ "total_regions": 5,
5
+ "total_ufs": 27
6
+ },
7
+ "regions": [
8
+ {
9
+ "code": "N",
10
+ "name_pt": "Norte",
11
+ "name_en": "North",
12
+ "ufs": ["AC", "AM", "AP", "PA", "RO", "RR", "TO"]
13
+ },
14
+ {
15
+ "code": "NE",
16
+ "name_pt": "Nordeste",
17
+ "name_en": "Northeast",
18
+ "ufs": ["AL", "BA", "CE", "MA", "PB", "PE", "PI", "RN", "SE"]
19
+ },
20
+ {
21
+ "code": "SE",
22
+ "name_pt": "Sudeste",
23
+ "name_en": "Southeast",
24
+ "ufs": ["ES", "MG", "RJ", "SP"]
25
+ },
26
+ {
27
+ "code": "S",
28
+ "name_pt": "Sul",
29
+ "name_en": "South",
30
+ "ufs": ["PR", "RS", "SC"]
31
+ },
32
+ {
33
+ "code": "CO",
34
+ "name_pt": "Centro-Oeste",
35
+ "name_en": "Central-West",
36
+ "ufs": ["DF", "GO", "MS", "MT"]
37
+ }
38
+ ],
39
+ "ufs": [
40
+ { "code": "AC", "ibge_code": "12", "name": "Acre", "region": "N", "capital": "Rio Branco" },
41
+ { "code": "AL", "ibge_code": "27", "name": "Alagoas", "region": "NE", "capital": "Maceió" },
42
+ { "code": "AM", "ibge_code": "13", "name": "Amazonas", "region": "N", "capital": "Manaus" },
43
+ { "code": "AP", "ibge_code": "16", "name": "Amapá", "region": "N", "capital": "Macapá" },
44
+ { "code": "BA", "ibge_code": "29", "name": "Bahia", "region": "NE", "capital": "Salvador" },
45
+ { "code": "CE", "ibge_code": "23", "name": "Ceará", "region": "NE", "capital": "Fortaleza" },
46
+ { "code": "DF", "ibge_code": "53", "name": "Distrito Federal", "region": "CO", "capital": "Brasília" },
47
+ { "code": "ES", "ibge_code": "32", "name": "Espírito Santo", "region": "SE", "capital": "Vitória" },
48
+ { "code": "GO", "ibge_code": "52", "name": "Goiás", "region": "CO", "capital": "Goiânia" },
49
+ { "code": "MA", "ibge_code": "21", "name": "Maranhão", "region": "NE", "capital": "São Luís" },
50
+ { "code": "MG", "ibge_code": "31", "name": "Minas Gerais", "region": "SE", "capital": "Belo Horizonte" },
51
+ { "code": "MS", "ibge_code": "50", "name": "Mato Grosso do Sul", "region": "CO", "capital": "Campo Grande" },
52
+ { "code": "MT", "ibge_code": "51", "name": "Mato Grosso", "region": "CO", "capital": "Cuiabá" },
53
+ { "code": "PA", "ibge_code": "15", "name": "Pará", "region": "N", "capital": "Belém" },
54
+ { "code": "PB", "ibge_code": "25", "name": "Paraíba", "region": "NE", "capital": "João Pessoa" },
55
+ { "code": "PE", "ibge_code": "26", "name": "Pernambuco", "region": "NE", "capital": "Recife" },
56
+ { "code": "PI", "ibge_code": "22", "name": "Piauí", "region": "NE", "capital": "Teresina" },
57
+ { "code": "PR", "ibge_code": "41", "name": "Paraná", "region": "S", "capital": "Curitiba" },
58
+ { "code": "RJ", "ibge_code": "33", "name": "Rio de Janeiro", "region": "SE", "capital": "Rio de Janeiro" },
59
+ { "code": "RN", "ibge_code": "24", "name": "Rio Grande do Norte", "region": "NE", "capital": "Natal" },
60
+ { "code": "RO", "ibge_code": "11", "name": "Rondônia", "region": "N", "capital": "Porto Velho" },
61
+ { "code": "RR", "ibge_code": "14", "name": "Roraima", "region": "N", "capital": "Boa Vista" },
62
+ { "code": "RS", "ibge_code": "43", "name": "Rio Grande do Sul", "region": "S", "capital": "Porto Alegre" },
63
+ { "code": "SC", "ibge_code": "42", "name": "Santa Catarina", "region": "S", "capital": "Florianópolis" },
64
+ { "code": "SE", "ibge_code": "28", "name": "Sergipe", "region": "NE", "capital": "Aracaju" },
65
+ { "code": "SP", "ibge_code": "35", "name": "São Paulo", "region": "SE", "capital": "São Paulo" },
66
+ { "code": "TO", "ibge_code": "17", "name": "Tocantins", "region": "N", "capital": "Palmas" }
67
+ ]
68
+ }