feat: datasus-brasil v0.1 — decoder DBC, CNES, CLI e mapeamento LOINC↔TUSS↔SIGTAP - #1
Conversation
Três camadas compondo o pipeline DBC → registros JS: - implode.ts: port do PKWARE DCL Implode a partir de pkwdcl.js (ConspiracyHu, Unlicense) + blast.c (Mark Adler, zlib). LZ77 com janela de 4096 bytes e Huffman canônico LSB-first. Zero deps. - dbc.ts: parser do envelope DATASUS. Layout verificado via hex dump real: DBF header em [0..H-1], 4 bytes de padding, payload DCL a partir de [H+4]. Corrige a descrição de DBC_FORMAT.md (offset 10 não se aplica a arquivos DATASUS reais). - dbf.ts: leitor xBase DBF mínimo in-memory (browser + Node). Suporta os tipos que DATASUS usa: C, N, F, D, L, I. Decode em windows-1252 por padrão. Async iterable de objetos JS prontos para JSON.stringify. API pública: - implodeDecompress(compressed, length) — descompressão pura - dbcToDbf(dbc) — DBC bytes → DBF bytes completos - readDbfRecords(dbf, options) — DBF → async iterable de registros - readDbcRecords(dbc, options) — atalho end-to-end Verificação: fixture real do FTP DATASUS (SIH-RD Acre 2024/01, 4315 registros × 702 bytes). 5/5 testes passando. Saída JSON-ready. tsconfig adiciona lib "DOM" para o TextDecoder tipado (global em Node e browser via @types/node + DOM). Zero deps nativas. Refs: PRE-198 M1
Primeira entrega funcional da façade @precisa-saude/datasus — consome o
decoder DBC + agrega + enriquece com dados IBGE, saída JSON end-to-end.
Componentes:
- ftp/: cliente basic-ftp com cache local em ~/.cache/datasus-brasil
(respeita XDG_CACHE_HOME). Preserva estrutura do servidor no cache.
Somente Node — browser deve consumir bytes de outro canal.
- datasets/sih/: loader alto-nível para SIH-RD. sih.load({ uf, year,
month }) resolve path FTP, baixa (com cache), decodifica via
@precisa-saude/datasus-dbc e retorna registros tipados. Versão
streaming também exposta (streamRecords). Validação de UF/ano/mês.
- labeling/: lookup de municípios por código IBGE. 5571 municípios
embarcados (167KB tight JSON). Aceita códigos de 6 dígitos (DATASUS)
ou 7 dígitos (IBGE/CNES) e normaliza internamente.
- aggregations/: countBy, countByNested, topN — helpers puros que
retornam objetos JS diretamente serializáveis para JSON.
Exemplo funcional em examples/sih-admissions-by-city.ts: baixa SIH-RD
para UF/ano/mês (default AC 2024/01), agrega internações por município,
enriquece com nomes IBGE e emite JSON no stdout. Testado manualmente
end-to-end com o FTP real do DATASUS.
examples/ passa a ser workspace pnpm com package.json próprio. tsx
adicionado como devDep para executar .ts diretamente.
Testes: 22 passing (aggregations, municipios, sih-paths, e2e-fixture
via RDAC2401.dbc). Cobertura funcional de toda a superfície pública.
Refs: PRE-198 M2
Expande a façade @precisa-saude/datasus com dois novos datasets
patient-facing, alimentando alertas geográficos (dengue/chikungunya/
zika) e lookup de estabelecimentos para agendamento.
SINAN (arboviroses, BR-wide anual):
- paths.ts — convenção /dissemin/publicos/SINAN/DADOS/{FINAIS,PRELIM}/
{AGRAVO}BR{YY}.dbc. Suporta DENG/CHIK/ZIKA. Preliminar opcional.
- types.ts — SinanArboviroseRecord com CLASSI_FIN, DT_NOTIFIC,
ID_MUNICIP, SG_UF_NOT como core estável.
- load.ts — sinan.load({ agravo, year }) e streamRecords para surtos
de alto volume.
- agravos.ts — labelAgravo('DENG') → 'Dengue'.
CNES (estabelecimentos e profissionais, por UF × mês):
- paths.ts — convenção /dissemin/publicos/CNES/200508_/Dados/{SUB}/
{SUB}{UF}{YYMM}.dbc. Suporta ST (estabelecimentos) e PF
(profissionais).
- types.ts — CnesEstabelecimentoRecord (CNES, CODUFMUN, TP_UNID,
FANTASIA, LATITUDE, LONGITUDE, ...) e CnesProfissionalRecord.
- load.ts — cnes.loadEstabelecimentos / loadProfissionais.
- tipos-unidade.ts — labelTipoUnidade('05') → 'Hospital Geral';
cobertura dos ~35 tipos mais comuns.
dbc: correção no leitor DBF para aceitar padding 0x00 como terminador
alternativo além de 0x0D. CNES DBFs reais (dBase III com extensão
VFP-like) usam 0x00 no final da área de descritores em vez do 0x0D
canônico — bug encontrado ao decodificar STAC2401.
Fixtures reais commitadas:
- ZIKABR20.dbc (560KB) — menor fixture SINAN disponível
- STAC2401.dbc (62KB) — CNES Acre, 1374 estabelecimentos
Exemplos em examples/sinan-dengue-by-uf.ts e cnes-establishments.ts,
saída JSON via stdout pronta para jq.
Testes: 41 passing (4 novos arquivos: sinan-paths, cnes-paths,
sinan-fixture, cnes-fixture). Pipeline completo verde.
Refs: PRE-198 M3
Reduz o escopo do v0.1 ao CNES — a rota via SIH/SINAN será revisitada se houver demanda patient-facing concreta. O roadmap passa a mirar SIA-SUS (ambulatorial) por conter os códigos SIGTAP que ligam biomarcadores do produto a procedimentos faturados pelo SUS. - remove datasets sih/ e sinan/ e seus testes (paths, fixtures, e2e) - remove fixture ZIKABR20.dbc e exemplos sih-*/sinan-* - ajusta packages/core/src/index.ts e datasets/index.ts - atualiza keywords/description de packages/core e CITATION.cff
- adiciona ProgressEvent e onProgress no download(): usa client.size() pra estimar total, emite evento inicial, intermediários (via trackProgress, ~500ms) e final; em cache hit, 1 evento único com fromCache: true - expõe streamEstabelecimentos / streamProfissionais em CNES para varredura single-pass com memória constante (consumido pelo --limit do CLI) - re-exporta DownloadOptions e ProgressEvent no barrel - cobre o client com testes unitários isolados de rede
CLI com saída JSON-first (default) e JSONL para streaming, consumindo streamEstabelecimentos do core. Stdout recebe o resultado; stderr recebe barra de progresso (TTY) ou linha-resumo (pipe), permitindo pipe direto pra jq. Módulos internos: - args parser minimal sem deps (--flag valor, --flag=valor, -h) - main dispatch testável (sem process.exit nos caminhos normais) - output serialização JSON/JSONL - stream consumo do async iterable com --limit e --raw - progress reporter TTY/pipe-aware com throttle Comando inicial: datasus-brasil cnes --uf <UF> --year <YYYY> --month <MM> com flags --top, --limit, --raw, --format. Cobre args/output/stream/progress/main/cnes com testes unitários (59 testes); o bin entry (src/index.ts) fica excluído de coverage por só fazer tradução de resultado em exit codes.
Pipeline pra ligar biomarcadores LOINC (via @precisa-saude/fhir) a códigos SIGTAP — pré-requisito pra consultar SIA-SUS (próximo dataset do roadmap). O mapeamento é comprometido como dado derivado de fontes oficiais: ANS regenera raramente, DATASUS publica o SIGTAP mensal. data/ (comprometido no pacote npm via files: ["data", "dist"]): - ans-tuss-sigtap-oficial.json TUSS↔SIGTAP oficial ANS (6919 linhas) - sigtap.json tabela SIGTAP completa (4982 procs) - loinc-tuss-sigtap.json mapeamento derivado fuzzy (164 bio) - loinc-tuss-sigtap.llm.json mapeamento refinado por Gemini 3.1 Pro - loinc-tuss-sigtap.report.md e loinc-tuss-sigtap.llm.report.md - fhir-brasil-tuss-audit.md + BRTUSSProcedimentosLabVS.fixed.fsh/.diff.md packages/core/scripts/ (zero deps além de basic-ftp): - extract-ans-xlsx.ts parse nativo do XLSX oficial (regex OOXML) - build-sigtap-mapping.ts mapeamento fuzzy biomarcador→TUSS→SIGTAP - fix-fhir-brasil-tuss.ts gera VS corrigido pro fhir-brasil scripts/llm-refine-mapping.ts (na raiz pra reuso fora do core): - reprocessa via LLM via OpenRouter, pega erros semânticos que o fuzzy não captura (Apo A vs Apo B, sangue oculto urina vs fezes, etc.) - checkpoint incremental em .llm.partial.json (gitignored) - cada decisão tem reasoning de 1-2 frases scripts/llm-debug-one.ts: helper pra inspecionar resposta crua do LLM. Ajustes de infra: - packages/core/package.json: expõe build:ans-mapping, build:sigtap-mapping, fix:fhir-brasil-tuss; files: ["data", "dist"] - raiz: script llm:refine-mapping - eslint.config.js: bloco scripts/**/*.ts e packages/*/scripts/**/*.ts com project: false, no-console off e type-imports consistentes - gitignore: *.llm.partial.json - packages/core/README.md: tabela de arquivos data/ e regeneração
A fixture RDAC2401.dbc vinha sendo tratada como se fosse um teste de decoder + validação do schema SIH-RD. Agora que SIH saiu do escopo do v0.1, o e2e passa a validar só o que o pacote datasus-dbc precisa: que o envelope DBC descomprime pra DBF, que o DBF tem campos bem-formados e que os 4315 registros decodificam sem erro. - comentário do src/dbc.ts não menciona mais SIH-RD - teste sanity-checka header.fields.length > 10 e nomes não-vazios em vez de UF_ZI/ANO_CMPT/MES_CMPT - nome do describe passa a descrever a fixture como payload
README raiz aponta CNES como v0.1 e SIA-SUS como roadmap (com justificativa da pivô por biomarcadores + SIGTAP). Passa a incluir seção de terminologia LOINC↔TUSS↔SIGTAP com fontes oficiais. CLAUDE.md, CONTRIBUTING.md, CONVENTIONS.md, DISCLAIMER.md: limpam menções a SIH e SINAN nos exemplos, alinhando o tom "CNES agora, outros datasets conforme demanda real".
Resolve conflitos em eslint.config.js, tsconfig.json e pnpm-lock.yaml adotando as configs compartilhadas @Precisa-Saude introduzidas em #2, mantendo `lib: ["ES2022", "DOM"]` para o decoder DBC usar TextDecoder.
Claude Review - Round 1SummaryMajor PR adding a CLI package ( Changes
🔍 Found 10 suggestions (see inline comments) Automated review by Claude Opus 4.6 - Round 1 of 2 | 58,254 in / 1,533 out | $0.3296 |
- args.ts: `--year -5` agora aceita número negativo como valor; `-vh` splita em bools individuais (POSIX) em vez de bucketar como string única. - stream.ts: substituir `optInt(..., 0)` com fallback morto por parse inline; mensagem de erro agora ecoa o valor recebido. - cnes.ts + stream.ts: `--raw --format json` passa a streamar um array JSON com memória constante via `emitJsonArrayStream`, eliminando risco de OOM em CNES-ST de UFs grandes. - BRTUSSProcedimentosLabVS.fixed.fsh: Apo B corrigido para TUSS 40301362 (colisão fuzzy fazia apontar para 40301354 de Apo A); linha `needs-review` de Hemograma removida por ser redundante com a ativa. - +3 testes cobrindo negativos e short flags combinados.
Claude Review - Round 2 (Final)SummaryLarge PR that adds a CLI package ( Changes
🔍 Found 6 suggestions (see inline comments) Automated review by Claude Opus 4.6 - Round 2 (Final) - No further reviews will be performed | 56,876 in / 973 out | $0.3087 |
- stream.ts: `emitJsonArrayStream` agora fecha o array dentro de try/finally, garantindo JSON válido mesmo se o source lançar mid-stream. - progress.ts: `startTime` capturado no primeiro evento não-cache em vez de na criação do reporter — delays de setup FTP não distorcem mais a velocidade reportada. - main.ts: lê VERSION de package.json em runtime (fim do drift manual); erro dedicado quando uma flag aparece antes do subcomando. - engines.node alinhado em >=22 nos três pacotes (match CI + .nvmrc + root). - tsup.config.ts: target node22.
## 1.0.0 (2026-04-22) ### Features * **ci:** adotar workflows canônicos split + doctor + publish-tag ([ea8194d](ea8194d)) * consumir @precisa-saude/agent-instructions + worktree-cli ([9e91ee0](9e91ee0)) * **core,cli:** terminologia pública + CNES record labeler (PRE-200) ([#6](#6)) ([b940743](b940743)) * datasus-brasil v0.1 — decoder DBC, CNES, CLI e mapeamento LOINC↔TUSS↔SIGTAP ([#1](#1)) ([27cd027](27cd027)) ### Bug Fixes * **ci:** concede contents: write no caller para _release.yml poder pedir ([08d5611](08d5611)), closes [#16](#16) * pre-push fallback — typecheck/test topológicos, só lint paralelo ([6b86449](6b86449)) ### Tests * **dbc:** cobrir caminhos de erro e decoders por tipo ([5bcae1d](5bcae1d)) * excluir scripts de build e arquivos types.ts da cobertura ([5287766](5287766)) ### CI/CD * drop --offline from pre-push pnpm install ([2834a16](2834a16)) * normalizar workflows e templates de PR/issue ([8eabc49](8eabc49)) ### Chores * alinhar hooks husky e turbo.json ao template compartilhado ([acd971a](acd971a)) * aplicar drift safe-only do precisa sync ([3ada96c](3ada96c)) * aplicar fixes do template husky (turbo detection + regex var) ([98ac50f](98ac50f)) * **config:** scaffold inicial do monorepo datasus-brasil ([04dc154](04dc154)) * **deps:** add renovate config ([808bd94](808bd94)) * **deps:** adotar configs compartilhadas [@Precisa-Saude](https://github.com/precisa-saude) ([a42c894](a42c894))
Sumário
PR inaugural do monorepo
datasus-brasil— decoder DBC puro TS/JS, façade de alto nível focada em CNES, CLI com saída JSON-first, e infraestrutura de terminologia LOINC↔TUSS↔SIGTAP preparando o próximo dataset do roadmap (SIA-SUS).Referências: PRE-197 (plano geral OSS patient-first) e PRE-198 (plano de execução datasus-brasil).
Pivô de escopo durante a execução
SIH-RD e SINAN (arboviroses), originalmente previstos como datasets do MVP em PRE-198, foram removidos antes do v0.1 final. O vetor de valor pro produto Precisa Saúde se desenhou mais forte via biomarcadores → SIGTAP → SIA-SUS, então o v0.1 sai com CNES apenas. A infraestrutura pra voltar com SIH/SINAN está pronta (decoder, FTP client, schemas por vintage) caso haja demanda concreta.
Commits nesta PR
Nove commits — três iniciais construindo SIH+SINAN+CNES, seis subsequentes realizando o pivô para CNES-only + CLI + mapeamento SIGTAP. O escopo final vive nos últimos seis.
Base (M1–M2 original)
04dc154chore(config) — scaffold do monorepo (pnpm+turbo+tsup+vitest+ESLint+Prettier+husky+commitlint), CI, licença Apache-2.0, docs pt-BR.1452b1bfeat(dbc) —@precisa-saude/datasus-dbc: decoder puro TS/JS em 3 camadas (DCL Implode port dopkwdcl.js+blast.c, parser do envelope DBC, leitor xBase DBF).4f605a6feat(core) — SIH-RD loader + FTP client + labeling IBGE + agregações. Superseded pelos commits seguintes — SIH removido em7177a71.4eff3b1feat(core) — SINAN (arboviroses) + CNES. SINAN removido em7177a71.Pivô para CNES-only + CLI + mapeamento (o foco desta PR)
7177a71refactor(core) — remove SIH e SINAN do escopo do v0.1, mantém só CNES. Ajusta barrel, package.json, CITATION.0c7f8a0feat(core) — adicionaProgressEvent/onProgressno download FTP (usaclient.size()pra estimar total, throttle viatrackProgress, cache hit emite evento único). ExpõestreamEstabelecimentos/streamProfissionaispra varredura single-pass com memória constante.30bea37feat(cli) — novo pacote@precisa-saude/datasus-clicom comandocnes. Args parser sem deps, dispatch testável semprocess.exitnos caminhos normais, progresso TTY/pipe-aware em stderr, saída JSON default / JSONL para streaming. 59 testes cobrem args/output/stream/progress/main/cnes.3d86921feat(core) — dados e scripts de terminologia LOINC↔TUSS↔SIGTAP. Ver seção abaixo.090e7c0test(dbc) — torna o e2e agnóstico ao schema da fixture (antes assumia campos do SIH-RD).10c49c6docs — README, CLAUDE.md, CONTRIBUTING, CONVENTIONS, DISCLAIMER alinhados ao novo escopo.Superfície pública (v0.1 final)
```ts
// Decoder puro (browser + Node)
import { readDbcRecords, dbcToDbf, implodeDecompress } from '@precisa-saude/datasus-dbc';
// Façade alto-nível
import {
cnes, // loaders + streams
download, type ProgressEvent, // FTP client com progresso
findMunicipio, labelTipoUnidade, // labeling IBGE + CNES
countBy, countByNested, topN, // agregações JSON-ready
} from '@precisa-saude/datasus';
// Streaming com memória constante
for await (const rec of cnes.streamEstabelecimentos({ uf: 'SP', year: 2024, month: 3 })) {
// ...
}
```
```bash
CLI
datasus-brasil cnes --uf AC --year 2024 --month 1 --top 5
datasus-brasil cnes --uf SP --year 2024 --month 1 --raw --limit 20 --format jsonl | jq '...'
```
Mapeamento LOINC↔TUSS↔SIGTAP (commit
3d86921)Dados derivados de fontes oficiais, comprometidos no pacote pra consumo downstream:
ans-tuss-sigtap-oficial.json— TUSS↔SIGTAP oficial da ANS (6919 linhas,MAPEAMENTO TUSS x SIGTAP 2017 04.xlsx)sigtap.json— tabela SIGTAP completa (4982 procedimentos da competência mais recente do DATASUS)loinc-tuss-sigtap.json— mapeamento fuzzy dos 164 biomarcadores LOINC do@precisa-saude/fhir→ TUSS → SIGTAPloinc-tuss-sigtap.llm.json— mapeamento refinado por Gemini 3.1 Pro via OpenRouter (corrige erros semânticos que o fuzzy comete: Apo A vs Apo B, sangue oculto urina vs fezes, CAC vs cálcio sérico, etc.)BRTUSSProcedimentosLabVS.fixed.fsh+ audit — VS corrigido pra PR upstream nofhir-brasilScripts:
build:ans-mapping(parse XLSX nativo via regex OOXML, zero deps),build:sigtap-mapping,fix:fhir-brasil-tuss,llm:refine-mapping.Por que esse escopo
CNES serve casos patient-facing imediatos — lookup de estabelecimentos por localização e tipo, base pra agendamento e recomendação. SIA-SUS é o próximo target do roadmap: contém os exames laboratoriais faturados pelo SUS via códigos SIGTAP, que a plataforma precisa ligar aos biomarcadores LOINC. O mapeamento ANS→SIGTAP distilado aqui é o pré-requisito dessa ligação.
Saúde do código
pnpm turbo run typecheck test lint buildpassando: 11/11 tarefas, 87/87 testes (28 core + 59 CLI)Plano de teste
7177a71em diante) — os 3 anteriores compunham o M1/M2 original e já foram parcialmente revertidosmaindatasus-dbc,datasus,datasus-cli)Artefatos externos usados como referência
blast.c(zlib, permissivo)pkwdcl.js(Unlicense — lift direto com atribuição)DBC_FORMAT.md(corrigido durante implementação)MAPEAMENTO TUSS x SIGTAP 2017 04.xlsx(dado aberto via Lei 12.527/2011)ftp.datasus.gov.br