susflow 0.1.1__py3-none-any.whl
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.
- susflow/__init__.py +3 -0
- susflow/cache.py +26 -0
- susflow/config.py +358 -0
- susflow/ftp.py +111 -0
- susflow/reader.py +97 -0
- susflow/requirements.txt +3 -0
- susflow/systems/__init__.py +3 -0
- susflow/systems/cnes.py +127 -0
- susflow/systems/pni.py +91 -0
- susflow/systems/siasus.py +128 -0
- susflow/systems/sihsus.py +166 -0
- susflow/systems/sim.py +190 -0
- susflow/systems/sinan.py +132 -0
- susflow/systems/sinasc.py +150 -0
- susflow-0.1.1.dist-info/METADATA +195 -0
- susflow-0.1.1.dist-info/RECORD +17 -0
- susflow-0.1.1.dist-info/WHEEL +4 -0
susflow/__init__.py
ADDED
susflow/cache.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
susflow/cache.py
|
|
3
|
+
================
|
|
4
|
+
Resolução de cache local. Compartilhado por todos os sistemas.
|
|
5
|
+
Pasta padrão: ~/.susflow/cache/, espelhando a estrutura do FTP.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
_CACHE_PADRAO = Path.home() / ".susflow" / "cache"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def caminho_local(caminho_ftp: str, raiz: Path | None = None) -> Path:
|
|
14
|
+
"""
|
|
15
|
+
Retorna o path local correspondente a um caminho FTP.
|
|
16
|
+
Ex: /dissemin/publicos/SINASC/NOV/DNRES/DNSP2022.dbc
|
|
17
|
+
→ ~/.susflow/cache/dissemin/publicos/SINASC/NOV/DNRES/DNSP2022.dbc
|
|
18
|
+
"""
|
|
19
|
+
raiz = Path(raiz) if raiz else _CACHE_PADRAO
|
|
20
|
+
# remove a barra inicial para não criar path absoluto ao fazer /
|
|
21
|
+
relativo = caminho_ftp.lstrip("/")
|
|
22
|
+
return raiz / relativo
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def existe(caminho_ftp: str, raiz: Path | None = None) -> bool:
|
|
26
|
+
return caminho_local(caminho_ftp, raiz).exists()
|
susflow/config.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
susflow/config.py
|
|
3
|
+
=================
|
|
4
|
+
Metadados de todos os sistemas do DATASUS.
|
|
5
|
+
|
|
6
|
+
Campos de cada sistema:
|
|
7
|
+
ftp_dir — caminho absoluto no FTP
|
|
8
|
+
pattern — padrão do nome ({UF}, {YYYY}, {YY}, {PREFIX}, {DISEASE}, {TYPE})
|
|
9
|
+
granularity — "year" | "month"
|
|
10
|
+
year_digits — 2 ou 4
|
|
11
|
+
format — "dbc" | "dbf" | "zip"
|
|
12
|
+
scope — "uf" | "national"
|
|
13
|
+
year_range — (ano_inicio, ano_fim) inclusive
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
FTP_HOST = "ftp.datasus.gov.br"
|
|
17
|
+
|
|
18
|
+
UFS = [
|
|
19
|
+
"AC", "AL", "AM", "AP", "BA", "CE", "DF", "ES", "GO",
|
|
20
|
+
"MA", "MG", "MS", "MT", "PA", "PB", "PE", "PI", "PR",
|
|
21
|
+
"RJ", "RN", "RO", "RR", "RS", "SC", "SE", "SP", "TO",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# SIM — /dissemin/publicos/SIM/CID10/
|
|
26
|
+
# DORES/ → DO{UF}{YYYY}.dbc por UF, anual, 4 dígitos
|
|
27
|
+
# DOFET/ → DO{TYPE}{YY}.dbc nacional, anual, 2 dígitos
|
|
28
|
+
# tipos: EXT, FET, INF, MAT
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
SIM = {
|
|
31
|
+
"description": "Sistema de Informações sobre Mortalidade",
|
|
32
|
+
"ftp_base": "/dissemin/publicos/SIM/CID10",
|
|
33
|
+
|
|
34
|
+
# Documentação técnica e layouts de campos
|
|
35
|
+
"docs": {
|
|
36
|
+
"ftp_dir": "/dissemin/publicos/SIM/CID10/DOCS",
|
|
37
|
+
"arquivos": {
|
|
38
|
+
"Docs_Tabs_CID10.zip": "Layouts, tabelas e dicionário de variáveis",
|
|
39
|
+
"Estrutura_do_SIM_2025.pdf": "Estrutura atual dos arquivos (referência principal)",
|
|
40
|
+
"Estrutura_SIM_Anterior.pdf": "Estrutura anterior — necessária para bases legadas",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
# Dados agregados tabulados
|
|
45
|
+
"tab": {
|
|
46
|
+
"ftp_dir": "/dissemin/publicos/SIM/CID10/TAB",
|
|
47
|
+
"arquivos": {
|
|
48
|
+
"OBITOS_CID10_TAB.zip": "Óbitos agregados por CID-10 (série histórica tabulada)",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
# Tabelas de apoio (CID-10, municípios, ocupações, países, UFs)
|
|
53
|
+
"tabelas": {
|
|
54
|
+
"ftp_dir": "/dissemin/publicos/SIM/CID10/TABELAS",
|
|
55
|
+
"arquivos": {
|
|
56
|
+
"CID10.DBF": "Classificação Internacional de Doenças — CID-10",
|
|
57
|
+
"CIDCAP10.DBF": "Capítulos do CID-10",
|
|
58
|
+
"CADMUN.DBF": "Cadastro de municípios",
|
|
59
|
+
"CADMUN.xls": "Cadastro de municípios (formato Excel)",
|
|
60
|
+
"TABOCUP.DBF": "Tabela de ocupações (CBO)",
|
|
61
|
+
"TABPAIS.DBF": "Tabela de países",
|
|
62
|
+
"TABUF.DBF": "Tabela de unidades federativas",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"uf": {
|
|
67
|
+
"ftp_dir": "/dissemin/publicos/SIM/CID10/DORES",
|
|
68
|
+
"pattern": "DO{UF}{YYYY}.dbc",
|
|
69
|
+
"granularity": "year",
|
|
70
|
+
"year_digits": 4,
|
|
71
|
+
"format": "dbc",
|
|
72
|
+
"scope": "uf",
|
|
73
|
+
"year_range": (1996, 2024),
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
"special": {
|
|
77
|
+
"ftp_dir": "/dissemin/publicos/SIM/CID10/DOFET",
|
|
78
|
+
"pattern": "DO{TYPE}{YY}.dbc",
|
|
79
|
+
"granularity": "year",
|
|
80
|
+
"year_digits": 2,
|
|
81
|
+
"format": "dbc",
|
|
82
|
+
"scope": "national",
|
|
83
|
+
"year_range": (1996, 2024),
|
|
84
|
+
"types": {
|
|
85
|
+
"EXT": "Óbitos por causas externas",
|
|
86
|
+
"FET": "Óbitos fetais",
|
|
87
|
+
"INF": "Óbitos infantis",
|
|
88
|
+
"MAT": "Óbitos maternos",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# SINASC — /dissemin/publicos/SINASC/NOV/
|
|
95
|
+
# DNRES/ → DN{UF}{YYYY}.dbc por UF, anual, 4 dígitos
|
|
96
|
+
# DNRES/ → DNBR{YYYY}.dbc agregado nacional (2014–2017 apenas)
|
|
97
|
+
# DNRES/ → DNEX{YYYY}.dbc exceções/suplementar (pontual, ex: 2021)
|
|
98
|
+
# DOCS/ → documentação técnica (caminho a confirmar — não mapeado ainda)
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
SINASC = {
|
|
101
|
+
"description": "Sistema de Informações sobre Nascidos Vivos",
|
|
102
|
+
|
|
103
|
+
"uf": {
|
|
104
|
+
"ftp_dir": "/dissemin/publicos/SINASC/NOV/DNRES",
|
|
105
|
+
"pattern": "DN{UF}{YYYY}.dbc",
|
|
106
|
+
"granularity": "year",
|
|
107
|
+
"year_digits": 4,
|
|
108
|
+
"format": "dbc",
|
|
109
|
+
"scope": "uf",
|
|
110
|
+
"year_range": (1996, 2022),
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
# Agregado nacional — série incompleta (apenas 2014–2017 confirmados no FTP)
|
|
114
|
+
"nacional": {
|
|
115
|
+
"ftp_dir": "/dissemin/publicos/SINASC/NOV/DNRES",
|
|
116
|
+
"pattern": "DNBR{YYYY}.dbc",
|
|
117
|
+
"granularity": "year",
|
|
118
|
+
"year_digits": 4,
|
|
119
|
+
"format": "dbc",
|
|
120
|
+
"scope": "national",
|
|
121
|
+
"year_range": (2014, 2017),
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
# Arquivo de exceção/suplementar — pontual, não é uma série regular
|
|
125
|
+
"excecoes": {
|
|
126
|
+
"ftp_dir": "/dissemin/publicos/SINASC/NOV/DNRES",
|
|
127
|
+
"pattern": "DNEX{YYYY}.dbc",
|
|
128
|
+
"format": "dbc",
|
|
129
|
+
"nota": "Arquivos pontuais com registros suplementares. Confirmado: DNEX2021.dbc.",
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
# Documentação técnica — caminho exato a confirmar (não mapeado ainda)
|
|
133
|
+
"docs": {
|
|
134
|
+
"ftp_dir": "/dissemin/publicos/SINASC/NOV/DOCS",
|
|
135
|
+
"arquivos": {
|
|
136
|
+
"Estrutura_SINASC_para_CD.pdf": "Estrutura dos arquivos (formato legado de CD-ROM)",
|
|
137
|
+
"Legislacao_PDF.pdf": "Legislação relacionada ao SINASC",
|
|
138
|
+
"NASC98.HLP": "Arquivo de ajuda do sistema legado (1998)",
|
|
139
|
+
"Portaria.pdf": "Portaria regulamentadora",
|
|
140
|
+
},
|
|
141
|
+
"nota": "Caminho FTP não confirmado — diretório DOCS não foi mapeado ainda.",
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# SINAN — /dissemin/publicos/SINAN/DADOS/
|
|
147
|
+
# FINAIS/{DISEASE}BR{YY}.dbc dados consolidados
|
|
148
|
+
# PRELIM/{DISEASE}BR{YY}.dbc dados preliminares
|
|
149
|
+
# DOCS/ documentação técnica (caminho a confirmar)
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
SINAN = {
|
|
152
|
+
"description": "Sistema de Informações de Agravos de Notificação",
|
|
153
|
+
"ftp_dir": "/dissemin/publicos/SINAN/DADOS/FINAIS",
|
|
154
|
+
"ftp_dir_prelim": "/dissemin/publicos/SINAN/DADOS/PRELIM",
|
|
155
|
+
"pattern": "{DISEASE}BR{YY}.dbc",
|
|
156
|
+
"granularity": "year",
|
|
157
|
+
"year_digits": 2,
|
|
158
|
+
"format": "dbc",
|
|
159
|
+
"scope": "national",
|
|
160
|
+
"diseases": {
|
|
161
|
+
"ACBI": "Acidente por animal peçonhento",
|
|
162
|
+
"ACGR": "Acidente de trabalho grave",
|
|
163
|
+
"ANIM": "Acidente por animal (outros)",
|
|
164
|
+
"ANTR": "Antraz (Carbúnculo)",
|
|
165
|
+
"BOTU": "Botulismo",
|
|
166
|
+
"CANC": "Câncer relacionado ao trabalho",
|
|
167
|
+
"CHAG": "Doença de Chagas",
|
|
168
|
+
"CHIK": "Chikungunya",
|
|
169
|
+
"COLE": "Cólera",
|
|
170
|
+
"COQU": "Coqueluche",
|
|
171
|
+
"DCRJ": "Doença de Creutzfeldt-Jakob",
|
|
172
|
+
"DENG": "Dengue",
|
|
173
|
+
"DERM": "Dermatose ocupacional",
|
|
174
|
+
"DIFT": "Difteria",
|
|
175
|
+
"ESPO": "Esporotricose",
|
|
176
|
+
"ESQU": "Esquistossomose",
|
|
177
|
+
"FMAC": "Febre maculosa",
|
|
178
|
+
"FTIF": "Febre tifoide",
|
|
179
|
+
"HANS": "Hanseníase",
|
|
180
|
+
"HANT": "Hantavirose",
|
|
181
|
+
"IEXO": "Intoxicação exógena",
|
|
182
|
+
"LEIV": "Leishmaniose visceral",
|
|
183
|
+
"LEPT": "Leptospirose",
|
|
184
|
+
"LERD": "LER/DORT",
|
|
185
|
+
"LTA": "Leishmaniose tegumentar americana",
|
|
186
|
+
"MALA": "Malária",
|
|
187
|
+
"MENI": "Meningite",
|
|
188
|
+
"MENT": "Transtorno mental relacionado ao trabalho",
|
|
189
|
+
"NTRA": "Noma (Cancrum oris)",
|
|
190
|
+
"PAIR": "Perda auditiva induzida por ruído",
|
|
191
|
+
"PEST": "Peste",
|
|
192
|
+
"PFAN": "Paralisia flácida aguda / Poliomielite",
|
|
193
|
+
"PNEU": "Pneumoconiose",
|
|
194
|
+
"RAIV": "Raiva humana",
|
|
195
|
+
"ROTA": "Rotavírus",
|
|
196
|
+
"SDTA": "Surto de doença transmitida por alimento",
|
|
197
|
+
"TETA": "Tétano acidental",
|
|
198
|
+
"TETN": "Tétano neonatal",
|
|
199
|
+
"TOXC": "Toxoplasmose congênita",
|
|
200
|
+
"TOXG": "Toxoplasmose gestacional",
|
|
201
|
+
"TRAC": "Tracoma",
|
|
202
|
+
"TUBE": "Tuberculose",
|
|
203
|
+
"VIOL": "Violência doméstica / sexual / autoprovocada",
|
|
204
|
+
"ZIKA": "Zika vírus",
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
# Documentação técnica — caminho FTP a confirmar (não mapeado ainda)
|
|
208
|
+
"docs": {
|
|
209
|
+
"ftp_dir": "/dissemin/publicos/SINAN/DOCS",
|
|
210
|
+
"arquivos": {
|
|
211
|
+
"Docs_TAB_SINAN.zip": "Layouts, tabelas e dicionário de variáveis de todos os agravos",
|
|
212
|
+
"POP_I_Acesso_a_Microdados_5.pdf": "Guia de acesso aos microdados do SINAN",
|
|
213
|
+
"POP_II_Descompactacao_expansao_conversao_3.pdf": "Guia de descompactação e conversão dos arquivos .dbc",
|
|
214
|
+
"POP_III_Instalacao_do_tabulador_TabWin_3.pdf": "Guia de instalação do TabWin (tabulador oficial)",
|
|
215
|
+
"Nota_Tecnica_Doenca_de_Creutzfeldt-Jakob(DCJ).pdf": "Nota técnica — Doença de Creutzfeldt-Jakob",
|
|
216
|
+
"Nota_Tecnica_Intoxicacao_Exogena.pdf": "Nota técnica — Intoxicação Exógena",
|
|
217
|
+
"Nota_Tecnica_Rotavirus.pdf": "Nota técnica — Rotavírus",
|
|
218
|
+
"Nota_Tecnica_Surtos_de_DTA.pdf": "Nota técnica — Surtos de Doença Transmitida por Alimento",
|
|
219
|
+
"Nota_Tecnica_Toxoplasmose.pdf": "Nota técnica — Toxoplasmose",
|
|
220
|
+
},
|
|
221
|
+
"nota": "Caminho FTP não confirmado — diretório DOCS não foi mapeado ainda.",
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# SIHSUS — /dissemin/publicos/SIHSUS/200801_/Dados/
|
|
227
|
+
# {PREFIX}{UF}{YY}{MM}.dbc por UF, mensal, 2 dígitos
|
|
228
|
+
# Prefixo principal: RD (AIH Reduzida — registro de internação)
|
|
229
|
+
# Exceção: CH e CM usam BR fixo (escopo nacional), não {UF}
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
SIHSUS = {
|
|
232
|
+
"description": "Sistema de Informações Hospitalares do SUS",
|
|
233
|
+
"ftp_dir": "/dissemin/publicos/SIHSUS/200801_/Dados",
|
|
234
|
+
"ftp_dir_old": "/dissemin/publicos/SIHSUS/199201_200712",
|
|
235
|
+
"pattern": "{PREFIX}{UF}{YY}{MM}.dbc",
|
|
236
|
+
"granularity": "month",
|
|
237
|
+
"year_digits": 2,
|
|
238
|
+
"format": "dbc",
|
|
239
|
+
"scope": "uf",
|
|
240
|
+
"year_range": (2008, 2026),
|
|
241
|
+
"prefixes": {
|
|
242
|
+
"RD": "AIH reduzida (internações — dado principal)",
|
|
243
|
+
"SP": "Serviços profissionais",
|
|
244
|
+
"RJ": "AIH rejeitada",
|
|
245
|
+
"ER": "AIH com erro",
|
|
246
|
+
},
|
|
247
|
+
# CH e CM usam BR fixo — padrão: {PREFIX}BR{YY}{MM}.dbc
|
|
248
|
+
"prefixes_nacionais": {
|
|
249
|
+
"CH": "Cabeçalho nacional (dados agregados)",
|
|
250
|
+
"CM": "Comunicação de movimento",
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
# SIASUS — /dissemin/publicos/SIASUS/200801_/Dados/
|
|
256
|
+
# {PREFIX}{UF}{YY}{MM}.dbc por UF, mensal, 2 dígitos
|
|
257
|
+
# Prefixo principal: PA (Produção Ambulatorial)
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
SIASUS = {
|
|
260
|
+
"description": "Sistema de Informações Ambulatoriais do SUS",
|
|
261
|
+
"ftp_dir": "/dissemin/publicos/SIASUS/200801_/Dados",
|
|
262
|
+
"ftp_dir_old": "/dissemin/publicos/SIASUS/199407_200712",
|
|
263
|
+
"pattern": "{PREFIX}{UF}{YY}{MM}.dbc",
|
|
264
|
+
"granularity": "month",
|
|
265
|
+
"year_digits": 2,
|
|
266
|
+
"format": "dbc",
|
|
267
|
+
"scope": "uf",
|
|
268
|
+
"year_range": (2008, 2026),
|
|
269
|
+
"prefixes": {
|
|
270
|
+
"PA": ("Produção Ambulatorial (BPA)", 2008, 2026),
|
|
271
|
+
"BI": ("BPA Individualizado", 2008, 2026),
|
|
272
|
+
"AD": ("APAC de Laudos Diversos", 2008, 2026),
|
|
273
|
+
"AM": ("APAC de Medicamentos", 2008, 2026),
|
|
274
|
+
"AMP": ("APAC de Medicamentos Padronizados", 2020, 2026),
|
|
275
|
+
"AQ": ("APAC de Quimioterapia", 2008, 2026),
|
|
276
|
+
"AR": ("APAC de Radioterapia", 2008, 2026),
|
|
277
|
+
"ACF": ("APAC Confecção de Fístula Arteriovenosa", 2014, 2026),
|
|
278
|
+
"ATD": ("APAC Tratamento Dialítico", 2014, 2026),
|
|
279
|
+
"PS": ("RAAS Psicossocial", 2013, 2026),
|
|
280
|
+
"AB": ("APAC Acompanhamento Pós Cirurgia Bariátrica (novo)", 2025, 2026),
|
|
281
|
+
"ABO": ("APAC Acompanhamento Pós Cirurgia Bariátrica (legado)", 2015, 2018),
|
|
282
|
+
"AN": ("APAC de Nefrologia (encerrado, substituído por ATD)", 2008, 2014),
|
|
283
|
+
"SAD": ("RAAS Atenção Domiciliar (encerrado)", 2013, 2015),
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# CNES — /dissemin/publicos/CNES/200508_/Dados/
|
|
289
|
+
# {TYPE}/{TYPE}{UF}{YY}{MM}.dbc por UF, mensal, 2 dígitos
|
|
290
|
+
# Arquivo fica 2 níveis dentro de Dados/: ex. ST/STSP2501.dbc
|
|
291
|
+
# Subtype principal: ST (Estabelecimentos)
|
|
292
|
+
# ---------------------------------------------------------------------------
|
|
293
|
+
CNES = {
|
|
294
|
+
"description": "Cadastro Nacional de Estabelecimentos de Saúde",
|
|
295
|
+
"ftp_base": "/dissemin/publicos/CNES/200508_/Dados",
|
|
296
|
+
"pattern": "{TYPE}/{TYPE}{UF}{YY}{MM}.dbc",
|
|
297
|
+
"granularity": "month",
|
|
298
|
+
"year_digits": 2,
|
|
299
|
+
"format": "dbc",
|
|
300
|
+
"scope": "uf",
|
|
301
|
+
"subtypes": {
|
|
302
|
+
"ST": ("Estabelecimentos (dado principal)", 2005, 2026),
|
|
303
|
+
"PF": ("Profissionais de saúde", 2005, 2026),
|
|
304
|
+
"DC": ("Dados complementares", 2005, 2026),
|
|
305
|
+
"EQ": ("Equipamentos", 2005, 2026),
|
|
306
|
+
"SR": ("Serviços especializados", 2005, 2026),
|
|
307
|
+
"LT": ("Leitos", 2005, 2026),
|
|
308
|
+
"HB": ("Habilitações", 2007, 2026),
|
|
309
|
+
"EF": ("Centros cirúrgicos e obstétricos", 2007, 2026),
|
|
310
|
+
"EP": ("Equipes de saúde", 2007, 2026),
|
|
311
|
+
"RC": ("Regras contratuais", 2007, 2026),
|
|
312
|
+
"IN": ("Incentivos", 2007, 2026),
|
|
313
|
+
"GM": ("Gestão e metas", 2014, 2026),
|
|
314
|
+
"EE": ("Equipamentos e produções (encerrado)", 2007, 2019),
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
# PNI — /dissemin/publicos/PNI/DADOS/
|
|
320
|
+
# DPNI{UF}{YY}.DBF por UF, anual, 2 dígitos
|
|
321
|
+
# Formato DBF puro (sem compressão blast) — usar dbfread, não pyreaddbc
|
|
322
|
+
# ---------------------------------------------------------------------------
|
|
323
|
+
PNI = {
|
|
324
|
+
"description": "Programa Nacional de Imunizações",
|
|
325
|
+
"ftp_dir": "/dissemin/publicos/PNI/DADOS",
|
|
326
|
+
"pattern": "DPNI{UF}{YY}.DBF",
|
|
327
|
+
"granularity": "year",
|
|
328
|
+
"year_digits": 2,
|
|
329
|
+
"format": "dbf",
|
|
330
|
+
"scope": "uf",
|
|
331
|
+
"year_range": (1994, 2019),
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
# IBGE/POP — /dissemin/publicos/IBGE/POP/
|
|
336
|
+
# POPBR{YY}.zip nacional, anual, 2 dígitos
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
IBGE_POP = {
|
|
339
|
+
"description": "Estimativas populacionais IBGE",
|
|
340
|
+
"ftp_dir": "/dissemin/publicos/IBGE/POP",
|
|
341
|
+
"pattern": "POPBR{YY}.zip",
|
|
342
|
+
"granularity": "year",
|
|
343
|
+
"year_digits": 2,
|
|
344
|
+
"format": "zip",
|
|
345
|
+
"scope": "national",
|
|
346
|
+
"year_range": (1980, 2012),
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
ALL_SYSTEMS = {
|
|
350
|
+
"SIM": SIM,
|
|
351
|
+
"SINASC": SINASC,
|
|
352
|
+
"SINAN": SINAN,
|
|
353
|
+
"SIHSUS": SIHSUS,
|
|
354
|
+
"SIASUS": SIASUS,
|
|
355
|
+
"CNES": CNES,
|
|
356
|
+
"PNI": PNI,
|
|
357
|
+
"IBGE_POP": IBGE_POP,
|
|
358
|
+
}
|
susflow/ftp.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
susflow/ftp.py
|
|
3
|
+
==============
|
|
4
|
+
Camada de transporte: toda comunicação com o FTP do DATASUS fica aqui.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from ftplib import FTP, all_errors
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .config import FTP_HOST
|
|
13
|
+
|
|
14
|
+
_TIMEOUT = 30 # segundos
|
|
15
|
+
_TENTATIVAS = 3
|
|
16
|
+
_BACKOFF = 2 # segundos entre tentativas
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FTPError(Exception):
|
|
20
|
+
"""Erro de comunicação com o FTP do DATASUS."""
|
|
21
|
+
|
|
22
|
+
class ArquivoNaoEncontradoError(FTPError):
|
|
23
|
+
"""Arquivo não existe no FTP."""
|
|
24
|
+
|
|
25
|
+
def _conectar() -> FTP:
|
|
26
|
+
"""Abre uma conexão FTP limpa. Reconectar por operação evita o bug '200 Type set to A'."""
|
|
27
|
+
ftp = FTP()
|
|
28
|
+
ftp.connect(FTP_HOST, 21, timeout=_TIMEOUT)
|
|
29
|
+
ftp.login()
|
|
30
|
+
ftp.set_pasv(True)
|
|
31
|
+
return ftp
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _tentar(fn, *args, **kwargs):
|
|
35
|
+
"""Executa fn com retentativas e backoff."""
|
|
36
|
+
ultimo_erro = None
|
|
37
|
+
for tentativa in range(_TENTATIVAS):
|
|
38
|
+
try:
|
|
39
|
+
return fn(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
except all_errors as e:
|
|
42
|
+
ultimo_erro = e
|
|
43
|
+
if tentativa < _TENTATIVAS - 1:
|
|
44
|
+
time.sleep(_BACKOFF)
|
|
45
|
+
|
|
46
|
+
raise FTPError(f"Falha após {_TENTATIVAS} tentativas: {ultimo_erro}") from ultimo_erro
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def listar(caminho: str) -> list[str]:
|
|
50
|
+
"""
|
|
51
|
+
Lista os nomes de arquivo em um diretório FTP.
|
|
52
|
+
Retorna apenas arquivos (não subdiretórios).
|
|
53
|
+
"""
|
|
54
|
+
def _listar():
|
|
55
|
+
ftp = _conectar()
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
itens: list[str] = []
|
|
59
|
+
ftp.retrlines("LIST", itens.append) # LIST no cwd após cwd()
|
|
60
|
+
ftp.cwd(caminho)
|
|
61
|
+
itens.clear()
|
|
62
|
+
ftp.retrlines("LIST", itens.append)
|
|
63
|
+
|
|
64
|
+
arquivos = []
|
|
65
|
+
for linha in itens:
|
|
66
|
+
if "<DIR>" in linha:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
nome = linha.split()[-1]
|
|
70
|
+
arquivos.append(nome)
|
|
71
|
+
|
|
72
|
+
return arquivos
|
|
73
|
+
|
|
74
|
+
finally:
|
|
75
|
+
ftp.quit()
|
|
76
|
+
|
|
77
|
+
return _tentar(_listar)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def baixar(caminho_ftp: str, destino: Path) -> Path:
|
|
81
|
+
"""
|
|
82
|
+
Baixa um arquivo do FTP para o caminho local `destino`.
|
|
83
|
+
Cria os diretórios necessários. Retorna o path do arquivo salvo.
|
|
84
|
+
Levanta ArquivoNaoEncontradoError se o arquivo não existir no FTP.
|
|
85
|
+
"""
|
|
86
|
+
destino = Path(destino)
|
|
87
|
+
destino.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
|
|
89
|
+
def _baixar():
|
|
90
|
+
ftp = _conectar()
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
with open(destino, "wb") as f:
|
|
94
|
+
ftp.retrbinary(f"RETR {caminho_ftp}", f.write)
|
|
95
|
+
|
|
96
|
+
except all_errors as e:
|
|
97
|
+
if destino.exists():
|
|
98
|
+
destino.unlink()
|
|
99
|
+
|
|
100
|
+
if "550" in str(e):
|
|
101
|
+
raise ArquivoNaoEncontradoError(
|
|
102
|
+
f"Arquivo não encontrado no FTP: {caminho_ftp}"
|
|
103
|
+
) from e
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
finally:
|
|
107
|
+
ftp.quit()
|
|
108
|
+
|
|
109
|
+
return destino
|
|
110
|
+
|
|
111
|
+
return _tentar(_baixar)
|
susflow/reader.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
susflow/reader.py
|
|
3
|
+
=================
|
|
4
|
+
Camada de leitura: converte arquivos locais (.dbc, .dbf, .zip) em DataFrame.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import tempfile
|
|
8
|
+
import zipfile
|
|
9
|
+
|
|
10
|
+
from pyreaddbc import dbc2dbf
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from dbfread import DBF
|
|
13
|
+
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LeituraError(Exception):
|
|
18
|
+
"""Falha ao converter arquivo para DataFrame."""
|
|
19
|
+
|
|
20
|
+
def ler(arquivo: Path) -> pd.DataFrame:
|
|
21
|
+
"""
|
|
22
|
+
Lê um arquivo local e retorna um DataFrame.
|
|
23
|
+
Suporta .dbc, .dbf e .zip (que contenha .dbc ou .dbf).
|
|
24
|
+
Colunas sempre em maiúsculo, strings decodificadas em latin-1.
|
|
25
|
+
"""
|
|
26
|
+
arquivo = Path(arquivo)
|
|
27
|
+
sufixo = arquivo.suffix.lower()
|
|
28
|
+
|
|
29
|
+
if sufixo == ".dbc":
|
|
30
|
+
return _ler_dbc(arquivo)
|
|
31
|
+
|
|
32
|
+
if sufixo == ".dbf":
|
|
33
|
+
return _ler_dbf(arquivo)
|
|
34
|
+
|
|
35
|
+
if sufixo == ".zip":
|
|
36
|
+
return _ler_zip(arquivo)
|
|
37
|
+
|
|
38
|
+
raise LeituraError(f"Formato não suportado: {sufixo}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _ler_dbc(arquivo: Path) -> pd.DataFrame:
|
|
42
|
+
try:
|
|
43
|
+
with tempfile.NamedTemporaryFile(suffix=".dbf", delete=False) as tmp:
|
|
44
|
+
tmp_path = Path(tmp.name)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
dbc2dbf(str(arquivo), str(tmp_path))
|
|
48
|
+
return _ler_dbf(tmp_path)
|
|
49
|
+
|
|
50
|
+
finally:
|
|
51
|
+
if tmp_path.exists():
|
|
52
|
+
tmp_path.unlink()
|
|
53
|
+
|
|
54
|
+
except LeituraError:
|
|
55
|
+
raise
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise LeituraError(f"Falha ao ler .dbc: {arquivo}") from e
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _ler_dbf(arquivo: Path) -> pd.DataFrame:
|
|
62
|
+
try:
|
|
63
|
+
tabela = DBF(str(arquivo), encoding="latin-1", load=True)
|
|
64
|
+
df = pd.DataFrame(iter(tabela))
|
|
65
|
+
df.columns = df.columns.str.upper()
|
|
66
|
+
return df
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise LeituraError(f"Falha ao ler .dbf: {arquivo}") from e
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _ler_zip(arquivo: Path) -> pd.DataFrame:
|
|
73
|
+
try:
|
|
74
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
75
|
+
|
|
76
|
+
with zipfile.ZipFile(arquivo) as zf:
|
|
77
|
+
nomes = [n for n in zf.namelist() if not n.endswith("/")]
|
|
78
|
+
|
|
79
|
+
if not nomes:
|
|
80
|
+
raise LeituraError(f"ZIP vazio: {arquivo}")
|
|
81
|
+
|
|
82
|
+
zf.extractall(tmp)
|
|
83
|
+
|
|
84
|
+
# lê o primeiro arquivo reconhecível dentro do zip
|
|
85
|
+
for nome in nomes:
|
|
86
|
+
extraido = Path(tmp) / nome
|
|
87
|
+
sufixo = extraido.suffix.lower()
|
|
88
|
+
if sufixo in (".dbc", ".dbf"):
|
|
89
|
+
return ler(extraido)
|
|
90
|
+
|
|
91
|
+
raise LeituraError(f"Nenhum .dbc ou .dbf encontrado dentro de {arquivo}")
|
|
92
|
+
|
|
93
|
+
except LeituraError:
|
|
94
|
+
raise
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise LeituraError(f"Falha ao ler .zip: {arquivo}") from e
|
susflow/requirements.txt
ADDED