agrobr 0.1.2__py3-none-any.whl → 0.5.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.
- agrobr/__init__.py +3 -2
- agrobr/benchmark/__init__.py +343 -0
- agrobr/cache/policies.py +3 -8
- agrobr/cepea/api.py +87 -30
- agrobr/cepea/client.py +0 -7
- agrobr/cli.py +141 -5
- agrobr/conab/api.py +72 -6
- agrobr/config.py +137 -0
- agrobr/constants.py +1 -2
- agrobr/contracts/__init__.py +186 -0
- agrobr/contracts/cepea.py +80 -0
- agrobr/contracts/conab.py +181 -0
- agrobr/contracts/ibge.py +146 -0
- agrobr/export.py +251 -0
- agrobr/health/__init__.py +10 -0
- agrobr/health/doctor.py +321 -0
- agrobr/http/browser.py +0 -9
- agrobr/ibge/api.py +104 -25
- agrobr/ibge/client.py +5 -20
- agrobr/models.py +100 -1
- agrobr/noticias_agricolas/client.py +0 -7
- agrobr/noticias_agricolas/parser.py +0 -17
- agrobr/plugins/__init__.py +205 -0
- agrobr/quality.py +319 -0
- agrobr/sla.py +249 -0
- agrobr/snapshots.py +321 -0
- agrobr/stability.py +148 -0
- agrobr/validators/semantic.py +447 -0
- {agrobr-0.1.2.dist-info → agrobr-0.5.0.dist-info}/METADATA +12 -12
- {agrobr-0.1.2.dist-info → agrobr-0.5.0.dist-info}/RECORD +33 -19
- {agrobr-0.1.2.dist-info → agrobr-0.5.0.dist-info}/WHEEL +0 -0
- {agrobr-0.1.2.dist-info → agrobr-0.5.0.dist-info}/entry_points.txt +0 -0
- {agrobr-0.1.2.dist-info → agrobr-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""Validacao semantica avancada para dados agricolas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import structlog
|
|
11
|
+
|
|
12
|
+
logger = structlog.get_logger()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ValidationResult:
|
|
17
|
+
"""Resultado de uma validacao."""
|
|
18
|
+
|
|
19
|
+
rule_name: str
|
|
20
|
+
severity: str
|
|
21
|
+
passed: bool
|
|
22
|
+
message: str
|
|
23
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SemanticRule(ABC):
|
|
28
|
+
"""Regra de validacao semantica base."""
|
|
29
|
+
|
|
30
|
+
name: str
|
|
31
|
+
description: str
|
|
32
|
+
severity: str = "warning"
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def check(self, df: pd.DataFrame, **kwargs: Any) -> list[ValidationResult]:
|
|
36
|
+
"""Executa verificacao. Retorna lista de resultados."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class PricePositiveRule(SemanticRule):
|
|
42
|
+
"""Preco deve ser positivo."""
|
|
43
|
+
|
|
44
|
+
name: str = "price_positive"
|
|
45
|
+
description: str = "Prices must be positive"
|
|
46
|
+
severity: str = "error"
|
|
47
|
+
|
|
48
|
+
def check(self, df: pd.DataFrame, **_kwargs: Any) -> list[ValidationResult]:
|
|
49
|
+
results: list[ValidationResult] = []
|
|
50
|
+
|
|
51
|
+
if "valor" not in df.columns:
|
|
52
|
+
return results
|
|
53
|
+
|
|
54
|
+
negative_mask = df["valor"] <= 0
|
|
55
|
+
if negative_mask.any():
|
|
56
|
+
negative_rows = df[negative_mask]
|
|
57
|
+
results.append(
|
|
58
|
+
ValidationResult(
|
|
59
|
+
rule_name=self.name,
|
|
60
|
+
severity=self.severity,
|
|
61
|
+
passed=False,
|
|
62
|
+
message=f"Found {len(negative_rows)} rows with non-positive prices",
|
|
63
|
+
details={
|
|
64
|
+
"count": len(negative_rows),
|
|
65
|
+
"sample": negative_rows.head(5).to_dict("records"),
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
results.append(
|
|
71
|
+
ValidationResult(
|
|
72
|
+
rule_name=self.name,
|
|
73
|
+
severity=self.severity,
|
|
74
|
+
passed=True,
|
|
75
|
+
message="All prices are positive",
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return results
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class ProductivityRangeRule(SemanticRule):
|
|
84
|
+
"""Produtividade deve estar em faixa razoavel."""
|
|
85
|
+
|
|
86
|
+
name: str = "productivity_range"
|
|
87
|
+
description: str = "Productivity must be within historical range"
|
|
88
|
+
severity: str = "warning"
|
|
89
|
+
ranges: dict[str, tuple[float, float]] = field(
|
|
90
|
+
default_factory=lambda: {
|
|
91
|
+
"soja": (1500, 5000),
|
|
92
|
+
"milho": (2000, 12000),
|
|
93
|
+
"arroz": (3000, 9000),
|
|
94
|
+
"trigo": (1500, 5000),
|
|
95
|
+
"feijao": (500, 3000),
|
|
96
|
+
"algodao": (1000, 2500),
|
|
97
|
+
"cafe": (1000, 4000),
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def check(self, df: pd.DataFrame, **_kwargs: Any) -> list[ValidationResult]:
|
|
102
|
+
results: list[ValidationResult] = []
|
|
103
|
+
|
|
104
|
+
if "produtividade" not in df.columns or "produto" not in df.columns:
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
violations = []
|
|
108
|
+
for _, row in df.iterrows():
|
|
109
|
+
produto = str(row.get("produto", "")).lower()
|
|
110
|
+
produtividade = row.get("produtividade")
|
|
111
|
+
|
|
112
|
+
if pd.isna(produtividade) or produto not in self.ranges:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
min_val, max_val = self.ranges[produto]
|
|
116
|
+
if produtividade < min_val or produtividade > max_val:
|
|
117
|
+
violations.append(
|
|
118
|
+
{
|
|
119
|
+
"produto": produto,
|
|
120
|
+
"produtividade": produtividade,
|
|
121
|
+
"range": f"[{min_val}, {max_val}]",
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if violations:
|
|
126
|
+
results.append(
|
|
127
|
+
ValidationResult(
|
|
128
|
+
rule_name=self.name,
|
|
129
|
+
severity=self.severity,
|
|
130
|
+
passed=False,
|
|
131
|
+
message=f"Found {len(violations)} rows with productivity outside range",
|
|
132
|
+
details={"violations": violations[:10]},
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
results.append(
|
|
137
|
+
ValidationResult(
|
|
138
|
+
rule_name=self.name,
|
|
139
|
+
severity=self.severity,
|
|
140
|
+
passed=True,
|
|
141
|
+
message="All productivity values within expected range",
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return results
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class DailyVariationRule(SemanticRule):
|
|
150
|
+
"""Variacao diaria nao deve ser extrema."""
|
|
151
|
+
|
|
152
|
+
name: str = "daily_variation"
|
|
153
|
+
description: str = "Daily variation should not exceed threshold"
|
|
154
|
+
severity: str = "warning"
|
|
155
|
+
max_variation_pct: float = 20.0
|
|
156
|
+
|
|
157
|
+
def check(self, df: pd.DataFrame, **_kwargs: Any) -> list[ValidationResult]:
|
|
158
|
+
results: list[ValidationResult] = []
|
|
159
|
+
|
|
160
|
+
if "valor" not in df.columns or "data" not in df.columns:
|
|
161
|
+
return results
|
|
162
|
+
|
|
163
|
+
df_sorted = df.sort_values("data")
|
|
164
|
+
df_sorted["variacao_calc"] = df_sorted["valor"].pct_change() * 100
|
|
165
|
+
|
|
166
|
+
extreme_mask = df_sorted["variacao_calc"].abs() > self.max_variation_pct
|
|
167
|
+
if extreme_mask.any():
|
|
168
|
+
extreme_rows = df_sorted[extreme_mask]
|
|
169
|
+
results.append(
|
|
170
|
+
ValidationResult(
|
|
171
|
+
rule_name=self.name,
|
|
172
|
+
severity=self.severity,
|
|
173
|
+
passed=False,
|
|
174
|
+
message=f"Found {len(extreme_rows)} rows with extreme daily variation (>{self.max_variation_pct}%)",
|
|
175
|
+
details={
|
|
176
|
+
"count": len(extreme_rows),
|
|
177
|
+
"max_variation": float(df_sorted["variacao_calc"].abs().max()),
|
|
178
|
+
"sample": extreme_rows[["data", "valor", "variacao_calc"]]
|
|
179
|
+
.head(5)
|
|
180
|
+
.to_dict("records"),
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
results.append(
|
|
186
|
+
ValidationResult(
|
|
187
|
+
rule_name=self.name,
|
|
188
|
+
severity=self.severity,
|
|
189
|
+
passed=True,
|
|
190
|
+
message=f"All daily variations within {self.max_variation_pct}% threshold",
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return results
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class DateSequenceRule(SemanticRule):
|
|
199
|
+
"""Datas devem estar em sequencia logica."""
|
|
200
|
+
|
|
201
|
+
name: str = "date_sequence"
|
|
202
|
+
description: str = "Dates should be in logical sequence"
|
|
203
|
+
severity: str = "warning"
|
|
204
|
+
|
|
205
|
+
def check(self, df: pd.DataFrame, **_kwargs: Any) -> list[ValidationResult]:
|
|
206
|
+
results: list[ValidationResult] = []
|
|
207
|
+
|
|
208
|
+
if "data" not in df.columns:
|
|
209
|
+
return results
|
|
210
|
+
|
|
211
|
+
df_sorted = df.sort_values("data")
|
|
212
|
+
|
|
213
|
+
if len(df_sorted) < 2:
|
|
214
|
+
return results
|
|
215
|
+
|
|
216
|
+
dates = pd.to_datetime(df_sorted["data"])
|
|
217
|
+
gaps = dates.diff().dt.days
|
|
218
|
+
|
|
219
|
+
large_gaps = gaps[gaps > 10].dropna()
|
|
220
|
+
if len(large_gaps) > 0:
|
|
221
|
+
results.append(
|
|
222
|
+
ValidationResult(
|
|
223
|
+
rule_name=self.name,
|
|
224
|
+
severity=self.severity,
|
|
225
|
+
passed=False,
|
|
226
|
+
message=f"Found {len(large_gaps)} gaps > 10 days in date sequence",
|
|
227
|
+
details={
|
|
228
|
+
"max_gap_days": int(gaps.max()),
|
|
229
|
+
"gap_count": len(large_gaps),
|
|
230
|
+
},
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
results.append(
|
|
235
|
+
ValidationResult(
|
|
236
|
+
rule_name=self.name,
|
|
237
|
+
severity=self.severity,
|
|
238
|
+
passed=True,
|
|
239
|
+
message="Date sequence is continuous",
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return results
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass
|
|
247
|
+
class AreaConsistencyRule(SemanticRule):
|
|
248
|
+
"""Area colhida nao pode ser maior que area plantada."""
|
|
249
|
+
|
|
250
|
+
name: str = "area_consistency"
|
|
251
|
+
description: str = "Harvested area cannot exceed planted area"
|
|
252
|
+
severity: str = "error"
|
|
253
|
+
|
|
254
|
+
def check(self, df: pd.DataFrame, **_kwargs: Any) -> list[ValidationResult]:
|
|
255
|
+
results: list[ValidationResult] = []
|
|
256
|
+
|
|
257
|
+
if "area_plantada" not in df.columns or "area_colhida" not in df.columns:
|
|
258
|
+
return results
|
|
259
|
+
|
|
260
|
+
mask = df["area_colhida"] > df["area_plantada"]
|
|
261
|
+
mask = mask & df["area_plantada"].notna() & df["area_colhida"].notna()
|
|
262
|
+
|
|
263
|
+
if mask.any():
|
|
264
|
+
violations = df[mask]
|
|
265
|
+
results.append(
|
|
266
|
+
ValidationResult(
|
|
267
|
+
rule_name=self.name,
|
|
268
|
+
severity=self.severity,
|
|
269
|
+
passed=False,
|
|
270
|
+
message=f"Found {len(violations)} rows where harvested area > planted area",
|
|
271
|
+
details={
|
|
272
|
+
"count": len(violations),
|
|
273
|
+
"sample": violations[["area_plantada", "area_colhida"]]
|
|
274
|
+
.head(5)
|
|
275
|
+
.to_dict("records"),
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
results.append(
|
|
281
|
+
ValidationResult(
|
|
282
|
+
rule_name=self.name,
|
|
283
|
+
severity=self.severity,
|
|
284
|
+
passed=True,
|
|
285
|
+
message="Area consistency validated",
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return results
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@dataclass
|
|
293
|
+
class SafraFormatRule(SemanticRule):
|
|
294
|
+
"""Safra deve estar no formato correto YYYY/YY."""
|
|
295
|
+
|
|
296
|
+
name: str = "safra_format"
|
|
297
|
+
description: str = "Safra must match format YYYY/YY"
|
|
298
|
+
severity: str = "error"
|
|
299
|
+
|
|
300
|
+
def check(self, df: pd.DataFrame, **_kwargs: Any) -> list[ValidationResult]:
|
|
301
|
+
import re
|
|
302
|
+
|
|
303
|
+
results: list[ValidationResult] = []
|
|
304
|
+
|
|
305
|
+
if "safra" not in df.columns:
|
|
306
|
+
return results
|
|
307
|
+
|
|
308
|
+
pattern = re.compile(r"^\d{4}/\d{2}$")
|
|
309
|
+
invalid = df[~df["safra"].astype(str).str.match(pattern)]
|
|
310
|
+
|
|
311
|
+
if len(invalid) > 0:
|
|
312
|
+
results.append(
|
|
313
|
+
ValidationResult(
|
|
314
|
+
rule_name=self.name,
|
|
315
|
+
severity=self.severity,
|
|
316
|
+
passed=False,
|
|
317
|
+
message=f"Found {len(invalid)} rows with invalid safra format",
|
|
318
|
+
details={
|
|
319
|
+
"count": len(invalid),
|
|
320
|
+
"invalid_values": invalid["safra"].unique().tolist()[:10],
|
|
321
|
+
},
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
else:
|
|
325
|
+
results.append(
|
|
326
|
+
ValidationResult(
|
|
327
|
+
rule_name=self.name,
|
|
328
|
+
severity=self.severity,
|
|
329
|
+
passed=True,
|
|
330
|
+
message="All safra values match expected format",
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return results
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
DEFAULT_RULES: list[SemanticRule] = [
|
|
338
|
+
PricePositiveRule(),
|
|
339
|
+
ProductivityRangeRule(),
|
|
340
|
+
DailyVariationRule(),
|
|
341
|
+
DateSequenceRule(),
|
|
342
|
+
AreaConsistencyRule(),
|
|
343
|
+
SafraFormatRule(),
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def validate_semantic(
|
|
348
|
+
df: pd.DataFrame,
|
|
349
|
+
rules: list[SemanticRule] | None = None,
|
|
350
|
+
fail_on_error: bool = False,
|
|
351
|
+
) -> tuple[bool, list[ValidationResult]]:
|
|
352
|
+
"""
|
|
353
|
+
Valida DataFrame semanticamente.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
df: DataFrame a validar
|
|
357
|
+
rules: Regras a aplicar (usa padrao se None)
|
|
358
|
+
fail_on_error: Se True, levanta excecao em erros
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Tupla (valido, lista de resultados)
|
|
362
|
+
"""
|
|
363
|
+
if rules is None:
|
|
364
|
+
rules = DEFAULT_RULES
|
|
365
|
+
|
|
366
|
+
all_results: list[ValidationResult] = []
|
|
367
|
+
|
|
368
|
+
for rule in rules:
|
|
369
|
+
try:
|
|
370
|
+
results = rule.check(df)
|
|
371
|
+
all_results.extend(results)
|
|
372
|
+
|
|
373
|
+
for r in results:
|
|
374
|
+
if not r.passed:
|
|
375
|
+
if r.severity == "error":
|
|
376
|
+
logger.error(
|
|
377
|
+
"semantic_validation",
|
|
378
|
+
rule=r.rule_name,
|
|
379
|
+
message=r.message,
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
logger.warning(
|
|
383
|
+
"semantic_validation",
|
|
384
|
+
rule=r.rule_name,
|
|
385
|
+
message=r.message,
|
|
386
|
+
)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
logger.error("semantic_rule_error", rule=rule.name, error=str(e))
|
|
389
|
+
all_results.append(
|
|
390
|
+
ValidationResult(
|
|
391
|
+
rule_name=rule.name,
|
|
392
|
+
severity="error",
|
|
393
|
+
passed=False,
|
|
394
|
+
message=f"Rule execution failed: {e}",
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
errors = [r for r in all_results if r.severity == "error" and not r.passed]
|
|
399
|
+
|
|
400
|
+
if errors and fail_on_error:
|
|
401
|
+
from agrobr.exceptions import ValidationError
|
|
402
|
+
|
|
403
|
+
raise ValidationError(
|
|
404
|
+
source="semantic",
|
|
405
|
+
field="multiple",
|
|
406
|
+
value=None,
|
|
407
|
+
reason=f"{len(errors)} semantic errors found: {[r.message for r in errors]}",
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
all_passed = all(r.passed for r in all_results)
|
|
411
|
+
return all_passed, all_results
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def get_validation_summary(results: list[ValidationResult]) -> dict[str, Any]:
|
|
415
|
+
"""Gera resumo das validacoes."""
|
|
416
|
+
total = len(results)
|
|
417
|
+
passed = sum(1 for r in results if r.passed)
|
|
418
|
+
failed = total - passed
|
|
419
|
+
|
|
420
|
+
errors = [r for r in results if r.severity == "error" and not r.passed]
|
|
421
|
+
warnings = [r for r in results if r.severity == "warning" and not r.passed]
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
"total_rules": total,
|
|
425
|
+
"passed": passed,
|
|
426
|
+
"failed": failed,
|
|
427
|
+
"errors": len(errors),
|
|
428
|
+
"warnings": len(warnings),
|
|
429
|
+
"success_rate": passed / total if total > 0 else 1.0,
|
|
430
|
+
"error_messages": [r.message for r in errors],
|
|
431
|
+
"warning_messages": [r.message for r in warnings],
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
__all__ = [
|
|
436
|
+
"SemanticRule",
|
|
437
|
+
"ValidationResult",
|
|
438
|
+
"PricePositiveRule",
|
|
439
|
+
"ProductivityRangeRule",
|
|
440
|
+
"DailyVariationRule",
|
|
441
|
+
"DateSequenceRule",
|
|
442
|
+
"AreaConsistencyRule",
|
|
443
|
+
"SafraFormatRule",
|
|
444
|
+
"DEFAULT_RULES",
|
|
445
|
+
"validate_semantic",
|
|
446
|
+
"get_validation_summary",
|
|
447
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agrobr
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Dados agrícolas brasileiros em uma linha de código
|
|
5
5
|
Project-URL: Homepage, https://github.com/bruno-portfolio/agrobr
|
|
6
6
|
Project-URL: Documentation, https://agrobr.dev
|
|
@@ -201,16 +201,16 @@ agrobr health --all
|
|
|
201
201
|
|
|
202
202
|
## Diferenciais
|
|
203
203
|
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
204
|
+
- **Async-first** para pipelines de alta performance
|
|
205
|
+
- **Cache inteligente** com DuckDB (analytics nativo)
|
|
206
|
+
- **Histórico permanente** - acumula dados automaticamente
|
|
207
|
+
- **Suporte pandas + polars**
|
|
208
|
+
- **Validação com Pydantic v2**
|
|
209
|
+
- **Validação estatística** de sanidade (detecta anomalias)
|
|
210
|
+
- **Fingerprinting de layout** para detecção proativa de mudanças
|
|
211
|
+
- **Alertas multi-canal** (Slack, Discord, Email)
|
|
212
|
+
- **CLI completo** para debug e automação
|
|
213
|
+
- **Fallback automático** entre fontes
|
|
214
214
|
|
|
215
215
|
## Como Funciona
|
|
216
216
|
|
|
@@ -226,7 +226,7 @@ Consultas a períodos antigos são instantâneas (cache). Apenas dados recentes
|
|
|
226
226
|
|
|
227
227
|
## Documentação
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
[Documentação completa](https://bruno-portfolio.github.io/agrobr/)
|
|
230
230
|
|
|
231
231
|
- [Guia Rápido](https://bruno-portfolio.github.io/agrobr/quickstart/)
|
|
232
232
|
- [API CEPEA](https://bruno-portfolio.github.io/agrobr/api/cepea/)
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
agrobr/__init__.py,sha256=
|
|
2
|
-
agrobr/cli.py,sha256=
|
|
3
|
-
agrobr/
|
|
1
|
+
agrobr/__init__.py,sha256=9z_yQ2XtFcXWp7KzN4ld0E8A8gfvVvHOnGXImwnNCjQ,286
|
|
2
|
+
agrobr/cli.py,sha256=rA7r6rsvZeYnLT_kRV2WkZ_lo-3ohNFqSga_vOhwl48,14861
|
|
3
|
+
agrobr/config.py,sha256=YHRkxpFrmvDzlLAzRIugpesrtWD0pVtsCnA48Xg39uQ,3696
|
|
4
|
+
agrobr/constants.py,sha256=arM-1KwgnpbULdp1Tj3zDNI9d0d8FRT9c-OPwrUjEnI,5019
|
|
4
5
|
agrobr/exceptions.py,sha256=zODmRKbqk7rRtkkFUclgHx-WriuTJG28zQs4JV5Zrow,2485
|
|
5
|
-
agrobr/
|
|
6
|
+
agrobr/export.py,sha256=ayJr8L6-Q4LYo_hBq14mE1Z0H7GISH2ZqinmlW_4aCE,7086
|
|
7
|
+
agrobr/models.py,sha256=VSCz5336elCwKK5rjsQHW-xyzfpO1EgwnnZSQBrAAaI,5848
|
|
8
|
+
agrobr/quality.py,sha256=mvgtgtnHLmMI0okoWWkvari_5763sLIuS2NF0Y61RBo,9647
|
|
9
|
+
agrobr/sla.py,sha256=SZN_tCNb9W7U0dhkLLvI600jfrcFjjklKI24y-otRV0,6594
|
|
10
|
+
agrobr/snapshots.py,sha256=B0ZNmcZFYtw_PSAJrwLyEC7GBrENLfCAy7OrFm7j5Ok,9151
|
|
11
|
+
agrobr/stability.py,sha256=t5tF01xCc24RKQYw_B8PTa9Rhp6SwvK370yugDkS75I,4081
|
|
6
12
|
agrobr/sync.py,sha256=j0tM1PZ2lT7uqHJpcqatWgJykg8WDtUQBCpdS1NXSbg,3432
|
|
7
13
|
agrobr/alerts/__init__.py,sha256=g5M62usjNdccmus9YCooPO9MxmwiOz_ds1AHEOoh1qE,186
|
|
8
14
|
agrobr/alerts/notifier.py,sha256=q7ajH37I-KvERhUMmL262rnBTZav9bjOCBfJtm3St6w,4981
|
|
15
|
+
agrobr/benchmark/__init__.py,sha256=oyzn2WwO0YYSR3Tc6x8ks3k0ecCeuLs0hbHkUONm4Rg,9317
|
|
9
16
|
agrobr/cache/__init__.py,sha256=T9g1cZ-1dJ33M4vkKNZZXKRLYk17B0Z3NgG3qZoFHJE,612
|
|
10
17
|
agrobr/cache/duckdb_store.py,sha256=c7I4uqNVF5WJ_8yB0TKiS8bZhsCx7mvQs3mIJRT3Q0I,13064
|
|
11
18
|
agrobr/cache/history.py,sha256=fdnwSCvEUKY4nAJNXvHKuAQVk3a_G4dGRR3RABJsjcA,8629
|
|
12
19
|
agrobr/cache/migrations.py,sha256=VoiRU1nCgruGWArOWDyEaDFzSdg6j4gttnPLJ4b1DBE,2723
|
|
13
|
-
agrobr/cache/policies.py,sha256=
|
|
20
|
+
agrobr/cache/policies.py,sha256=qh--bqp-XbqOw4PcA4eL4rKaWfmoYN3OBvvYUAyistM,8267
|
|
14
21
|
agrobr/cepea/__init__.py,sha256=QXYOJG_OoDpYYdtc1xvEnwKCdYppI6EmQg9tF0XA5Do,213
|
|
15
|
-
agrobr/cepea/api.py,sha256=
|
|
16
|
-
agrobr/cepea/client.py,sha256=
|
|
22
|
+
agrobr/cepea/api.py,sha256=GAgAVp68EqssFEhXOnHRpxTC2WdK7R5nuh-mji96eyI,13517
|
|
23
|
+
agrobr/cepea/client.py,sha256=lAHqUbQ65nlREo6WjV7N6p6JLGsXD7rNbRE4b2jLyt0,7690
|
|
17
24
|
agrobr/cepea/parsers/__init__.py,sha256=Cda0xDbu_gfkg90fTOngmnpRITKYJ0BIV2Y5Pymo5aM,970
|
|
18
25
|
agrobr/cepea/parsers/base.py,sha256=PdrL68ioeXw_ekp5AEwVCvK64Yt02ybmbZOANusei0I,948
|
|
19
26
|
agrobr/cepea/parsers/consensus.py,sha256=2J03DNUbVJRMQGrnz_FyHDlY6uqBb1zmHav2rzYSZX8,8732
|
|
@@ -21,38 +28,45 @@ agrobr/cepea/parsers/detector.py,sha256=cQq58t08UjrsBXKuai51NjcExvDxaDloBlteZ1pl
|
|
|
21
28
|
agrobr/cepea/parsers/fingerprint.py,sha256=HG2_s9fX6403IxB9oU9jOOMIkL7hnFl1RFJ2o2ZJtME,7382
|
|
22
29
|
agrobr/cepea/parsers/v1.py,sha256=ai1iKHZtXsTMp6M2crr8Mp4RTIvTamUx3JJEtYVUrQo,9907
|
|
23
30
|
agrobr/conab/__init__.py,sha256=-2T4uYh0hmL2WUUfETPc8etlbwvR-eE4QlhfaEb83fo,330
|
|
24
|
-
agrobr/conab/api.py,sha256=
|
|
31
|
+
agrobr/conab/api.py,sha256=U_pYMjORQ_V7oRDUgewhisuCh9dzMAeWgkqdYKhn5BY,7914
|
|
25
32
|
agrobr/conab/client.py,sha256=BF-VOUKy-ZTpgMQEH9lGsiWKig4YxnQf2PZYqOgXmOw,6113
|
|
26
33
|
agrobr/conab/parsers/__init__.py,sha256=QtDUjHnbgCr118I_301SPmx3guFK3DMmNfxjd9rPnXo,137
|
|
27
34
|
agrobr/conab/parsers/v1.py,sha256=2pDFTDZ_n28bBNuvjI8N8RdPidI_VvaJFjZb4XH3sCM,12716
|
|
28
|
-
agrobr/
|
|
35
|
+
agrobr/contracts/__init__.py,sha256=uSsgS42LEJpbIa1Xb7d9xfhY1-e9S0UUDVpuWgOKNPk,5691
|
|
36
|
+
agrobr/contracts/cepea.py,sha256=-FWXAcDjSr6Ed7anB3o3aXRYNKV71FQThYQHPa1yYzA,2266
|
|
37
|
+
agrobr/contracts/conab.py,sha256=5hajNz8WV3qUxNA1RaBQ7idY-3bH85lnoV9Du19b0s4,5028
|
|
38
|
+
agrobr/contracts/ibge.py,sha256=6ANCEGs5Y-wtBCWqrV4Q-z-M0VO1F1T1XzKFNBHXrTQ,3990
|
|
39
|
+
agrobr/health/__init__.py,sha256=he8IuCIdVEgaprzcXx8ICCpuJR2mQC6phs42x17WMPQ,571
|
|
29
40
|
agrobr/health/checker.py,sha256=prtjubt4m-ygLWGeIRAkc2oFP7_qDqAeeNtMv2P2hEg,5855
|
|
41
|
+
agrobr/health/doctor.py,sha256=VxN_34A3gOJXQky9LJy7BTENfkXYZOvJ2gtw5fKuLA8,9711
|
|
30
42
|
agrobr/health/reporter.py,sha256=2TO2C2NU39RFPuyZmX3bgn1NUEY95b9I9gpJVDcmYYY,10046
|
|
31
43
|
agrobr/http/__init__.py,sha256=tEc9cyPnMWhwwulC89Mlu-DKI6qb9HtFGSFIC07YaiY,327
|
|
32
|
-
agrobr/http/browser.py,sha256=
|
|
44
|
+
agrobr/http/browser.py,sha256=wtbf3QUHv5pB5j32niXp20U1KZEfkNQys-ETpLFB_fs,5603
|
|
33
45
|
agrobr/http/rate_limiter.py,sha256=jZrny-CB_KFW6U9iN76PQM60U8SyOLO7dG3cDkQy45c,2099
|
|
34
46
|
agrobr/http/retry.py,sha256=DTwQVoF79FcfPDvmVgd4AjvIcoX912akVGrblDRGRnw,2765
|
|
35
47
|
agrobr/http/user_agents.py,sha256=sl0HeMnxwHmNKgqantXZePTUo3hwyfgqFunEUphQVv0,2765
|
|
36
48
|
agrobr/ibge/__init__.py,sha256=hfSTOMKBnpVxlsOxKobY4fpgyuDNbgO_JuNXCZju9t4,262
|
|
37
|
-
agrobr/ibge/api.py,sha256=
|
|
38
|
-
agrobr/ibge/client.py,sha256=
|
|
49
|
+
agrobr/ibge/api.py,sha256=zRlAtaAvh-PsmHAorT6LzvoH8TR_zZCBCG_bQPKK-30,9858
|
|
50
|
+
agrobr/ibge/client.py,sha256=fSid4e_sarsukWXWOGq7pzQ-qKBcQD8AA39D0Q_gaEE,5807
|
|
39
51
|
agrobr/normalize/__init__.py,sha256=K7pKKTlTC1iPtsv31U5A_C1ZbBCG9ePdUWORjaFHPx0,1346
|
|
40
52
|
agrobr/normalize/dates.py,sha256=t2dHd7IS-3joUkQN7jAvdBKLwvVPanY7AIWy27tlxfY,7021
|
|
41
53
|
agrobr/normalize/encoding.py,sha256=vMSJsD_1mxgWn51MBGf4M4yayp5ct3iJpnD1HP22yUA,2842
|
|
42
54
|
agrobr/normalize/regions.py,sha256=PIGyb4zruK2PCS1YVrMuWFXMIQxw18rX3O8HY1uU0jY,7643
|
|
43
55
|
agrobr/normalize/units.py,sha256=MCAIXoeCxRdk4gi3tYjqyn7j8p3wdIO_vbb3mU_e4bE,7467
|
|
44
56
|
agrobr/noticias_agricolas/__init__.py,sha256=jI-4dUvX-IR6mQALzntyWHzgCYvFKApA5rmbG_1-QGs,269
|
|
45
|
-
agrobr/noticias_agricolas/client.py,sha256=
|
|
46
|
-
agrobr/noticias_agricolas/parser.py,sha256=
|
|
57
|
+
agrobr/noticias_agricolas/client.py,sha256=a9MehptdfDBvuckv_rcE_dftkO-cVG_XQP7PewvdWbM,6380
|
|
58
|
+
agrobr/noticias_agricolas/parser.py,sha256=ATBD0D1wtF8BmoGCzp4QUVzLumSbOj7ezzrghOUMHuE,4512
|
|
59
|
+
agrobr/plugins/__init__.py,sha256=rohBymL8BqboclbTRYUoyyrzKLECQoCumoca5bJRgx8,4768
|
|
47
60
|
agrobr/telemetry/__init__.py,sha256=9B-h84G0B3Do10UkyLerjeV7gYVxONP_TdmB1cmho0M,305
|
|
48
61
|
agrobr/telemetry/collector.py,sha256=U91E68ZPI9T-u81qwC5aEyRwb-Zs4iPyE3mnt1xIxUI,4059
|
|
49
62
|
agrobr/utils/__init__.py,sha256=3NdxvsGHGrHIYZPfQeUsjmPTb7Ci0jTBwMYIM5q5dN0,88
|
|
50
63
|
agrobr/utils/logging.py,sha256=CgSfZSrLAR3Awx_LC0jzZhyZTussPz8-22uDzBI7FqE,1708
|
|
51
64
|
agrobr/validators/__init__.py,sha256=6lq_iLVgy9_tbxvgaLIei_E0TuzJYpILV7q12f7gXHg,745
|
|
52
65
|
agrobr/validators/sanity.py,sha256=LCL4Fi9KVPUoSjvjeel0-vvOYGkHpS8ASqC8sGiMoro,8808
|
|
66
|
+
agrobr/validators/semantic.py,sha256=C480fA7DUqy0AAZbjjK-dkeL2vfsMTrO1SjwDhob0pI,13748
|
|
53
67
|
agrobr/validators/structural.py,sha256=sQzzVoSVi50zHd9oHBAk6eBSlL7ep9CX-XHQEDSDGIw,9348
|
|
54
|
-
agrobr-0.
|
|
55
|
-
agrobr-0.
|
|
56
|
-
agrobr-0.
|
|
57
|
-
agrobr-0.
|
|
58
|
-
agrobr-0.
|
|
68
|
+
agrobr-0.5.0.dist-info/METADATA,sha256=MQFaFN2kH0ei-Jgri9OCm126dk0KLllFwk7hVBuH0Pg,7874
|
|
69
|
+
agrobr-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
70
|
+
agrobr-0.5.0.dist-info/entry_points.txt,sha256=coFf6hQniKkja1iHtSR-Q5xtEwdPBciTjsllHVw02qc,42
|
|
71
|
+
agrobr-0.5.0.dist-info/licenses/LICENSE,sha256=nbehzK8ZLuQrIhwvqVn1rdRp1livNvGvutr4Sl6bxEs,1067
|
|
72
|
+
agrobr-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|