agrobr 0.1.0__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.
Files changed (58) hide show
  1. agrobr/__init__.py +10 -0
  2. agrobr/alerts/__init__.py +7 -0
  3. agrobr/alerts/notifier.py +167 -0
  4. agrobr/cache/__init__.py +31 -0
  5. agrobr/cache/duckdb_store.py +433 -0
  6. agrobr/cache/history.py +317 -0
  7. agrobr/cache/migrations.py +82 -0
  8. agrobr/cache/policies.py +240 -0
  9. agrobr/cepea/__init__.py +7 -0
  10. agrobr/cepea/api.py +360 -0
  11. agrobr/cepea/client.py +273 -0
  12. agrobr/cepea/parsers/__init__.py +37 -0
  13. agrobr/cepea/parsers/base.py +35 -0
  14. agrobr/cepea/parsers/consensus.py +300 -0
  15. agrobr/cepea/parsers/detector.py +108 -0
  16. agrobr/cepea/parsers/fingerprint.py +226 -0
  17. agrobr/cepea/parsers/v1.py +305 -0
  18. agrobr/cli.py +323 -0
  19. agrobr/conab/__init__.py +21 -0
  20. agrobr/conab/api.py +239 -0
  21. agrobr/conab/client.py +219 -0
  22. agrobr/conab/parsers/__init__.py +7 -0
  23. agrobr/conab/parsers/v1.py +383 -0
  24. agrobr/constants.py +205 -0
  25. agrobr/exceptions.py +104 -0
  26. agrobr/health/__init__.py +23 -0
  27. agrobr/health/checker.py +202 -0
  28. agrobr/health/reporter.py +314 -0
  29. agrobr/http/__init__.py +9 -0
  30. agrobr/http/browser.py +214 -0
  31. agrobr/http/rate_limiter.py +69 -0
  32. agrobr/http/retry.py +93 -0
  33. agrobr/http/user_agents.py +67 -0
  34. agrobr/ibge/__init__.py +19 -0
  35. agrobr/ibge/api.py +273 -0
  36. agrobr/ibge/client.py +256 -0
  37. agrobr/models.py +85 -0
  38. agrobr/normalize/__init__.py +64 -0
  39. agrobr/normalize/dates.py +303 -0
  40. agrobr/normalize/encoding.py +102 -0
  41. agrobr/normalize/regions.py +308 -0
  42. agrobr/normalize/units.py +278 -0
  43. agrobr/noticias_agricolas/__init__.py +6 -0
  44. agrobr/noticias_agricolas/client.py +222 -0
  45. agrobr/noticias_agricolas/parser.py +187 -0
  46. agrobr/sync.py +147 -0
  47. agrobr/telemetry/__init__.py +17 -0
  48. agrobr/telemetry/collector.py +153 -0
  49. agrobr/utils/__init__.py +5 -0
  50. agrobr/utils/logging.py +59 -0
  51. agrobr/validators/__init__.py +35 -0
  52. agrobr/validators/sanity.py +286 -0
  53. agrobr/validators/structural.py +313 -0
  54. agrobr-0.1.0.dist-info/METADATA +243 -0
  55. agrobr-0.1.0.dist-info/RECORD +58 -0
  56. agrobr-0.1.0.dist-info/WHEEL +4 -0
  57. agrobr-0.1.0.dist-info/entry_points.txt +2 -0
  58. agrobr-0.1.0.dist-info/licenses/LICENSE +21 -0
agrobr/ibge/api.py ADDED
@@ -0,0 +1,273 @@
1
+ """API pública do módulo IBGE - PAM e LSPA."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+ import pandas as pd
8
+ import structlog
9
+
10
+ from agrobr.ibge import client
11
+
12
+ logger = structlog.get_logger()
13
+
14
+
15
+ async def pam(
16
+ produto: str,
17
+ ano: int | str | list[int] | None = None,
18
+ uf: str | None = None,
19
+ nivel: Literal["brasil", "uf", "municipio"] = "uf",
20
+ variaveis: list[str] | None = None,
21
+ as_polars: bool = False,
22
+ ) -> pd.DataFrame:
23
+ """
24
+ Obtém dados da Produção Agrícola Municipal (PAM).
25
+
26
+ Args:
27
+ produto: Nome do produto (soja, milho, arroz, feijao, trigo, etc)
28
+ ano: Ano ou lista de anos (default: último disponível)
29
+ uf: Filtrar por UF (ex: "MT", "PR"). Requer nivel="uf" ou "municipio"
30
+ nivel: Nível territorial ("brasil", "uf", "municipio")
31
+ variaveis: Lista de variáveis (area_plantada, area_colhida, producao, rendimento)
32
+ as_polars: Se True, retorna polars.DataFrame
33
+
34
+ Returns:
35
+ DataFrame com dados da PAM
36
+
37
+ Example:
38
+ >>> df = await ibge.pam('soja', ano=2023)
39
+ >>> df = await ibge.pam('milho', ano=[2020, 2021, 2022], uf='MT')
40
+ """
41
+ logger.info(
42
+ "ibge_pam_request",
43
+ produto=produto,
44
+ ano=ano,
45
+ uf=uf,
46
+ nivel=nivel,
47
+ )
48
+
49
+ # Mapeia produto para código SIDRA
50
+ produto_lower = produto.lower()
51
+ if produto_lower not in client.PRODUTOS_PAM:
52
+ raise ValueError(
53
+ f"Produto não suportado: {produto}. Disponíveis: {list(client.PRODUTOS_PAM.keys())}"
54
+ )
55
+
56
+ produto_cod = client.PRODUTOS_PAM[produto_lower]
57
+
58
+ # Mapeia variáveis
59
+ if variaveis is None:
60
+ variaveis = ["area_plantada", "area_colhida", "producao", "rendimento"]
61
+
62
+ var_codes = []
63
+ for var in variaveis:
64
+ if var in client.VARIAVEIS:
65
+ var_codes.append(client.VARIAVEIS[var])
66
+ else:
67
+ logger.warning(f"Variável desconhecida: {var}")
68
+
69
+ # Mapeia nível territorial
70
+ nivel_map = {
71
+ "brasil": "1",
72
+ "uf": "3",
73
+ "municipio": "6",
74
+ }
75
+ territorial_level = nivel_map.get(nivel, "3")
76
+
77
+ # Define código territorial
78
+ ibge_code = "all"
79
+ if uf and nivel in ("uf", "municipio"):
80
+ ibge_code = client.uf_to_ibge_code(uf)
81
+
82
+ # Define período
83
+ if ano is None:
84
+ period = "last"
85
+ elif isinstance(ano, list):
86
+ period = ",".join(str(a) for a in ano)
87
+ else:
88
+ period = str(ano)
89
+
90
+ # Busca dados
91
+ df = await client.fetch_sidra(
92
+ table_code=client.TABELAS["pam_nova"],
93
+ territorial_level=territorial_level,
94
+ ibge_territorial_code=ibge_code,
95
+ variable=",".join(var_codes) if var_codes else None,
96
+ period=period,
97
+ classifications={"782": produto_cod},
98
+ )
99
+
100
+ # Processa resposta
101
+ df = client.parse_sidra_response(df)
102
+
103
+ # Pivota para ter variáveis como colunas
104
+ if "variavel" in df.columns and "valor" in df.columns:
105
+ df_pivot = df.pivot_table(
106
+ index=["localidade", "ano"] if "localidade" in df.columns else ["ano"],
107
+ columns="variavel",
108
+ values="valor",
109
+ aggfunc="first",
110
+ ).reset_index()
111
+
112
+ # Renomeia colunas para nomes mais simples
113
+ rename_map = {
114
+ "Área plantada": "area_plantada",
115
+ "Área colhida": "area_colhida",
116
+ "Quantidade produzida": "producao",
117
+ "Rendimento médio da produção": "rendimento",
118
+ "Valor da produção": "valor_producao",
119
+ }
120
+ df_pivot = df_pivot.rename(columns=rename_map)
121
+ df = df_pivot
122
+
123
+ df["produto"] = produto_lower
124
+ df["fonte"] = "ibge_pam"
125
+
126
+ if as_polars:
127
+ try:
128
+ import polars as pl
129
+
130
+ return pl.from_pandas(df) # type: ignore[no-any-return]
131
+ except ImportError:
132
+ logger.warning("polars_not_installed", fallback="pandas")
133
+
134
+ logger.info(
135
+ "ibge_pam_success",
136
+ produto=produto,
137
+ records=len(df),
138
+ )
139
+
140
+ return df
141
+
142
+
143
+ async def lspa(
144
+ produto: str,
145
+ ano: int | str | None = None,
146
+ mes: int | str | None = None,
147
+ uf: str | None = None,
148
+ as_polars: bool = False,
149
+ ) -> pd.DataFrame:
150
+ """
151
+ Obtém dados do Levantamento Sistemático da Produção Agrícola (LSPA).
152
+
153
+ O LSPA fornece estimativas mensais de safra para os principais produtos.
154
+
155
+ Args:
156
+ produto: Nome do produto (soja, milho_1, milho_2, arroz, feijao_1, etc)
157
+ ano: Ano de referência (default: atual)
158
+ mes: Mês de referência (1-12). Se None, retorna todos os meses do ano.
159
+ uf: Filtrar por UF (ex: "MT", "PR")
160
+ as_polars: Se True, retorna polars.DataFrame
161
+
162
+ Returns:
163
+ DataFrame com estimativas LSPA
164
+
165
+ Example:
166
+ >>> df = await ibge.lspa('soja', ano=2024)
167
+ >>> df = await ibge.lspa('milho_1', ano=2024, mes=6, uf='PR')
168
+ """
169
+ logger.info(
170
+ "ibge_lspa_request",
171
+ produto=produto,
172
+ ano=ano,
173
+ mes=mes,
174
+ uf=uf,
175
+ )
176
+
177
+ # Mapeia produto para código SIDRA
178
+ produto_lower = produto.lower()
179
+ if produto_lower not in client.PRODUTOS_LSPA:
180
+ raise ValueError(
181
+ f"Produto não suportado: {produto}. Disponíveis: {list(client.PRODUTOS_LSPA.keys())}"
182
+ )
183
+
184
+ produto_cod = client.PRODUTOS_LSPA[produto_lower]
185
+
186
+ # Define período
187
+ if ano is None:
188
+ from datetime import date
189
+
190
+ ano = date.today().year
191
+
192
+ # Define período
193
+ period = f"{ano}{int(mes):02d}" if mes else ",".join(f"{ano}{m:02d}" for m in range(1, 13))
194
+
195
+ # Define nível territorial
196
+ territorial_level = "3" if uf else "1"
197
+ ibge_code = client.uf_to_ibge_code(uf) if uf else "all"
198
+
199
+ # Busca dados (não especifica variáveis - retorna todas)
200
+ df = await client.fetch_sidra(
201
+ table_code=client.TABELAS["lspa"],
202
+ territorial_level=territorial_level,
203
+ ibge_territorial_code=ibge_code,
204
+ period=period,
205
+ classifications={"48": produto_cod},
206
+ )
207
+
208
+ # Processa resposta
209
+ df = client.parse_sidra_response(df)
210
+
211
+ # Adiciona período da consulta
212
+ df["ano"] = ano
213
+ if mes:
214
+ df["mes"] = mes
215
+
216
+ df["produto"] = produto_lower
217
+ df["fonte"] = "ibge_lspa"
218
+
219
+ if as_polars:
220
+ try:
221
+ import polars as pl
222
+
223
+ return pl.from_pandas(df) # type: ignore[no-any-return]
224
+ except ImportError:
225
+ logger.warning("polars_not_installed", fallback="pandas")
226
+
227
+ logger.info(
228
+ "ibge_lspa_success",
229
+ produto=produto,
230
+ records=len(df),
231
+ )
232
+
233
+ return df
234
+
235
+
236
+ async def produtos_pam() -> list[str]:
237
+ """
238
+ Lista produtos disponíveis na PAM.
239
+
240
+ Returns:
241
+ Lista de nomes de produtos
242
+
243
+ Example:
244
+ >>> prods = await ibge.produtos_pam()
245
+ >>> print(prods)
246
+ ['soja', 'milho', 'arroz', ...]
247
+ """
248
+ return list(client.PRODUTOS_PAM.keys())
249
+
250
+
251
+ async def produtos_lspa() -> list[str]:
252
+ """
253
+ Lista produtos disponíveis no LSPA.
254
+
255
+ Returns:
256
+ Lista de nomes de produtos
257
+
258
+ Example:
259
+ >>> prods = await ibge.produtos_lspa()
260
+ >>> print(prods)
261
+ ['soja', 'milho_1', 'milho_2', ...]
262
+ """
263
+ return list(client.PRODUTOS_LSPA.keys())
264
+
265
+
266
+ async def ufs() -> list[str]:
267
+ """
268
+ Lista UFs disponíveis.
269
+
270
+ Returns:
271
+ Lista de siglas de UF
272
+ """
273
+ return list(client.get_uf_codes().keys())
agrobr/ibge/client.py ADDED
@@ -0,0 +1,256 @@
1
+ """Cliente para API SIDRA do IBGE usando sidrapy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import pandas as pd
8
+ import sidrapy
9
+ import structlog
10
+
11
+ from agrobr import constants
12
+ from agrobr.http.rate_limiter import RateLimiter
13
+
14
+ logger = structlog.get_logger()
15
+
16
+
17
+ # Códigos das tabelas SIDRA
18
+ TABELAS = {
19
+ # PAM - Produção Agrícola Municipal
20
+ "pam_temporarias": "1612", # Lavouras temporárias (1974-2018)
21
+ "pam_permanentes": "1613", # Lavouras permanentes (1974-2018)
22
+ "pam_nova": "5457", # Nova série PAM (2018+)
23
+ # LSPA - Levantamento Sistemático da Produção Agrícola
24
+ "lspa": "6588", # Série mensal (2006+)
25
+ "lspa_safra": "1618", # Por ano de safra
26
+ }
27
+
28
+ # Variáveis disponíveis
29
+ VARIAVEIS = {
30
+ # PAM 5457
31
+ "area_plantada": "214",
32
+ "area_colhida": "215",
33
+ "producao": "216",
34
+ "rendimento": "112",
35
+ "valor_producao": "215",
36
+ # PAM 1612 (lavouras temporárias)
37
+ "area_plantada_1612": "109",
38
+ "area_colhida_1612": "1000109",
39
+ "producao_1612": "214",
40
+ "rendimento_1612": "112",
41
+ "valor_1612": "215",
42
+ # LSPA 6588
43
+ "area_lspa": "109",
44
+ "producao_lspa": "216",
45
+ "rendimento_lspa": "112",
46
+ }
47
+
48
+ # Níveis territoriais
49
+ NIVEIS_TERRITORIAIS = {
50
+ "brasil": "1",
51
+ "regiao": "2",
52
+ "uf": "3",
53
+ "mesorregiao": "7",
54
+ "microrregiao": "8",
55
+ "municipio": "6",
56
+ }
57
+
58
+ # Códigos de produtos agrícolas (classificação 782 para tabela 5457)
59
+ PRODUTOS_PAM = {
60
+ "soja": "40124",
61
+ "milho": "40126",
62
+ "arroz": "40117",
63
+ "feijao": "40120",
64
+ "trigo": "40145",
65
+ "algodao": "40109",
66
+ "cafe": "40112",
67
+ "cana": "40114",
68
+ "mandioca": "40127",
69
+ "laranja": "40125",
70
+ }
71
+
72
+ # Códigos para LSPA (classificação 48 para tabela 6588)
73
+ PRODUTOS_LSPA = {
74
+ "soja": "39443",
75
+ "milho_1": "39441",
76
+ "milho_2": "39442",
77
+ "arroz": "39432",
78
+ "feijao_1": "39436",
79
+ "feijao_2": "39437",
80
+ "feijao_3": "39438",
81
+ "trigo": "39447",
82
+ "algodao": "39433",
83
+ "cafe": "109194",
84
+ "amendoim_1": "109180",
85
+ "amendoim_2": "109181",
86
+ "aveia": "109179",
87
+ "batata_1": "39434",
88
+ "batata_2": "39435",
89
+ "cevada": "109182",
90
+ "mamona": "109183",
91
+ "sorgo": "109184",
92
+ "triticale": "109185",
93
+ }
94
+
95
+
96
+ async def fetch_sidra(
97
+ table_code: str,
98
+ territorial_level: str = "1",
99
+ ibge_territorial_code: str = "all",
100
+ variable: str | list[str] | None = None,
101
+ period: str | list[str] | None = None,
102
+ classifications: dict[str, str | list[str]] | None = None,
103
+ header: str = "n",
104
+ ) -> pd.DataFrame:
105
+ """
106
+ Busca dados do SIDRA/IBGE usando sidrapy.
107
+
108
+ Args:
109
+ table_code: Código da tabela SIDRA
110
+ territorial_level: Nível territorial (1=Brasil, 3=UF, 6=Município)
111
+ ibge_territorial_code: Código territorial IBGE ou "all"
112
+ variable: Código(s) da variável
113
+ period: Período (ex: "2023", "last 5", "2019-2023")
114
+ classifications: Classificações/filtros adicionais
115
+ header: "n" para header numérico, "y" para descritivo
116
+
117
+ Returns:
118
+ DataFrame com dados do SIDRA
119
+ """
120
+ logger.info(
121
+ "ibge_fetch_start",
122
+ table=table_code,
123
+ level=territorial_level,
124
+ period=period,
125
+ )
126
+
127
+ async with RateLimiter.acquire(constants.Fonte.IBGE):
128
+ # sidrapy é síncrono, então apenas chamamos diretamente
129
+ kwargs: dict[str, Any] = {
130
+ "table_code": table_code,
131
+ "territorial_level": territorial_level,
132
+ "ibge_territorial_code": ibge_territorial_code,
133
+ "header": header,
134
+ }
135
+
136
+ if variable:
137
+ if isinstance(variable, list):
138
+ kwargs["variable"] = ",".join(variable)
139
+ else:
140
+ kwargs["variable"] = variable
141
+
142
+ if period:
143
+ if isinstance(period, list):
144
+ kwargs["period"] = ",".join(period)
145
+ else:
146
+ kwargs["period"] = period
147
+
148
+ if classifications:
149
+ kwargs["classifications"] = classifications
150
+
151
+ try:
152
+ df = sidrapy.get_table(**kwargs)
153
+
154
+ # Remove primeira linha que é o header descritivo
155
+ if header == "n" and len(df) > 1:
156
+ df = df.iloc[1:].reset_index(drop=True)
157
+
158
+ logger.info(
159
+ "ibge_fetch_success",
160
+ table=table_code,
161
+ rows=len(df),
162
+ )
163
+
164
+ return pd.DataFrame(df)
165
+
166
+ except Exception as e:
167
+ logger.error(
168
+ "ibge_fetch_error",
169
+ table=table_code,
170
+ error=str(e),
171
+ )
172
+ raise
173
+
174
+
175
+ def parse_sidra_response(
176
+ df: pd.DataFrame,
177
+ rename_columns: dict[str, str] | None = None,
178
+ ) -> pd.DataFrame:
179
+ """
180
+ Processa resposta do SIDRA para formato mais legível.
181
+
182
+ Args:
183
+ df: DataFrame retornado pelo sidrapy
184
+ rename_columns: Mapeamento de renomeação de colunas
185
+
186
+ Returns:
187
+ DataFrame processado
188
+ """
189
+ # Mapeamento padrão de colunas SIDRA
190
+ default_rename = {
191
+ "NC": "nivel_territorial_cod",
192
+ "NN": "nivel_territorial",
193
+ "MC": "localidade_cod",
194
+ "MN": "localidade",
195
+ "V": "valor",
196
+ "D1C": "ano_cod",
197
+ "D1N": "ano",
198
+ "D2C": "variavel_cod",
199
+ "D2N": "variavel",
200
+ "D3C": "produto_cod",
201
+ "D3N": "produto",
202
+ "D4C": "classificacao_cod",
203
+ "D4N": "classificacao",
204
+ }
205
+
206
+ if rename_columns:
207
+ default_rename.update(rename_columns)
208
+
209
+ # Renomeia apenas colunas que existem
210
+ rename_map = {k: v for k, v in default_rename.items() if k in df.columns}
211
+ df = df.rename(columns=rename_map)
212
+
213
+ # Converte valor para numérico
214
+ if "valor" in df.columns:
215
+ df["valor"] = pd.to_numeric(df["valor"], errors="coerce")
216
+
217
+ return df
218
+
219
+
220
+ def get_uf_codes() -> dict[str, str]:
221
+ """Retorna mapeamento de sigla UF para código IBGE."""
222
+ return {
223
+ "RO": "11",
224
+ "AC": "12",
225
+ "AM": "13",
226
+ "RR": "14",
227
+ "PA": "15",
228
+ "AP": "16",
229
+ "TO": "17",
230
+ "MA": "21",
231
+ "PI": "22",
232
+ "CE": "23",
233
+ "RN": "24",
234
+ "PB": "25",
235
+ "PE": "26",
236
+ "AL": "27",
237
+ "SE": "28",
238
+ "BA": "29",
239
+ "MG": "31",
240
+ "ES": "32",
241
+ "RJ": "33",
242
+ "SP": "35",
243
+ "PR": "41",
244
+ "SC": "42",
245
+ "RS": "43",
246
+ "MS": "50",
247
+ "MT": "51",
248
+ "GO": "52",
249
+ "DF": "53",
250
+ }
251
+
252
+
253
+ def uf_to_ibge_code(uf: str) -> str:
254
+ """Converte sigla UF para código IBGE."""
255
+ codes = get_uf_codes()
256
+ return codes.get(uf.upper(), uf)
agrobr/models.py ADDED
@@ -0,0 +1,85 @@
1
+ """Modelos Pydantic v2 do agrobr."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import date, datetime
6
+ from decimal import Decimal
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, Field, field_validator
10
+
11
+ from .constants import Fonte
12
+
13
+
14
+ class Indicador(BaseModel):
15
+ fonte: Fonte
16
+ produto: str = Field(..., min_length=2)
17
+ praca: str | None = None
18
+ data: date
19
+ valor: Decimal = Field(..., gt=0)
20
+ unidade: str
21
+ metodologia: str | None = None
22
+ revisao: int = Field(default=0, ge=0)
23
+ meta: dict[str, Any] = Field(default_factory=dict)
24
+
25
+ parsed_at: datetime = Field(default_factory=datetime.utcnow)
26
+ parser_version: int = Field(default=1)
27
+ anomalies: list[str] = Field(default_factory=list)
28
+
29
+ @field_validator("produto")
30
+ @classmethod
31
+ def lowercase_produto(cls, v: str) -> str:
32
+ if isinstance(v, str):
33
+ return v.lower().strip()
34
+ return v
35
+
36
+
37
+ class Safra(BaseModel):
38
+ fonte: Fonte
39
+ produto: str
40
+ safra: str = Field(..., pattern=r"^\d{4}/\d{2}$")
41
+ uf: str | None = Field(None, min_length=2, max_length=2)
42
+ area_plantada: Decimal | None = Field(None, ge=0)
43
+ producao: Decimal | None = Field(None, ge=0)
44
+ produtividade: Decimal | None = Field(None, ge=0)
45
+ unidade_area: str = Field(default="mil_ha")
46
+ unidade_producao: str = Field(default="mil_ton")
47
+ levantamento: int = Field(..., ge=1, le=12)
48
+ data_publicacao: date
49
+ meta: dict[str, Any] = Field(default_factory=dict)
50
+
51
+ parsed_at: datetime = Field(default_factory=datetime.utcnow)
52
+ parser_version: int = Field(default=1)
53
+ anomalies: list[str] = Field(default_factory=list)
54
+
55
+
56
+ class CacheEntry(BaseModel):
57
+ key: str
58
+ data: bytes
59
+ created_at: datetime
60
+ expires_at: datetime
61
+ source: Fonte
62
+ version: int = 1
63
+ stale: bool = False
64
+ hit_count: int = 0
65
+
66
+
67
+ class HistoryEntry(BaseModel):
68
+ key: str
69
+ data: bytes
70
+ source: Fonte
71
+ data_date: date
72
+ collected_at: datetime
73
+ parser_version: int
74
+ fingerprint_hash: str
75
+
76
+
77
+ class Fingerprint(BaseModel):
78
+ source: Fonte
79
+ url: str
80
+ collected_at: datetime
81
+ table_classes: list[list[str]]
82
+ key_ids: list[str]
83
+ structure_hash: str
84
+ table_headers: list[list[str]]
85
+ element_counts: dict[str, int]
@@ -0,0 +1,64 @@
1
+ """Normalização de dados - unidades, datas, regiões, encoding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .dates import (
6
+ anos_para_safra,
7
+ lista_safras,
8
+ normalizar_safra,
9
+ periodo_safra,
10
+ safra_anterior,
11
+ safra_atual,
12
+ safra_para_anos,
13
+ safra_posterior,
14
+ validar_safra,
15
+ )
16
+ from .encoding import decode_content, detect_encoding
17
+ from .regions import (
18
+ ibge_para_uf,
19
+ listar_regioes,
20
+ listar_ufs,
21
+ normalizar_municipio,
22
+ normalizar_praca,
23
+ normalizar_uf,
24
+ uf_para_ibge,
25
+ uf_para_nome,
26
+ uf_para_regiao,
27
+ validar_uf,
28
+ )
29
+ from .units import (
30
+ converter,
31
+ preco_saca_para_tonelada,
32
+ preco_tonelada_para_saca,
33
+ sacas_para_toneladas,
34
+ toneladas_para_sacas,
35
+ )
36
+
37
+ __all__: list[str] = [
38
+ "decode_content",
39
+ "detect_encoding",
40
+ "converter",
41
+ "preco_saca_para_tonelada",
42
+ "preco_tonelada_para_saca",
43
+ "sacas_para_toneladas",
44
+ "toneladas_para_sacas",
45
+ "safra_atual",
46
+ "validar_safra",
47
+ "normalizar_safra",
48
+ "safra_para_anos",
49
+ "anos_para_safra",
50
+ "safra_anterior",
51
+ "safra_posterior",
52
+ "lista_safras",
53
+ "periodo_safra",
54
+ "normalizar_uf",
55
+ "uf_para_nome",
56
+ "uf_para_regiao",
57
+ "uf_para_ibge",
58
+ "ibge_para_uf",
59
+ "listar_ufs",
60
+ "listar_regioes",
61
+ "normalizar_municipio",
62
+ "normalizar_praca",
63
+ "validar_uf",
64
+ ]