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.
@@ -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.1.2
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
- - 🚀 **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
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
- 📚 [Documentação completa](https://bruno-portfolio.github.io/agrobr/)
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=8F6sBJaY9pRQCbyKBx8u4IhlZBRuwyYZE4sExT_b554,239
2
- agrobr/cli.py,sha256=Xi772yw-axkNFeeXFI0aYsJpB1LOjPCbf5Is1ZheZc0,10122
3
- agrobr/constants.py,sha256=TPMVRr4JJZ9CLq-FEpVk2HHbLIV0fjaqAjFYofstfsI,5130
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/models.py,sha256=UsV5yOzwT2zLUWswqhylWXNYAZOn1bGB45l2cGLd-PQ,2256
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=tAdizRchk69YFW1n3yCVajHGduB6GyFBeuNUhDKe14I,8642
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=FkOGqIuh_sJj-QHF-MG1nLJbam9ndvHMtSmtqvsquc0,11767
16
- agrobr/cepea/client.py,sha256=xb5ba9ESKLoqY84orR_U3vOGb91CqIWzjdJl3o5Avs4,8058
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=l-mFo2RAiwxQ15KjYXi7MQOPACfY2nD_3UIz9dGRc6k,5764
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/health/__init__.py,sha256=D78Istswk7-IKpwrkDEOi8wpzKQre2ftGJpB09wIvlM,383
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=CsicdVJBXqPRpYFk1zBbyz4h2iZzJF-rZ_RfHo2W3nw,6006
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=9NydjberpCF5MO9rtfLOSsgwC5k1EP190nec4SeRn80,7355
38
- agrobr/ibge/client.py,sha256=qDvx8NxNR0cM3msXKhuemTTLtFDdz8pWx-t62Edxdlw,6588
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=tVP7Kzl5V4Mz8XptV_h6_NXLrV6onuQ0gjBSjh_LGA4,6725
46
- agrobr/noticias_agricolas/parser.py,sha256=rpDORBwCMa9uTTwE8nqgRJ2C5mqhUQoG5nFuf9XmFdM,5172
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.1.2.dist-info/METADATA,sha256=q13sB1SHIQkSg3ERCDHOjsgAOAOXqY02U_RzMvzPhMA,7920
55
- agrobr-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
56
- agrobr-0.1.2.dist-info/entry_points.txt,sha256=coFf6hQniKkja1iHtSR-Q5xtEwdPBciTjsllHVw02qc,42
57
- agrobr-0.1.2.dist-info/licenses/LICENSE,sha256=nbehzK8ZLuQrIhwvqVn1rdRp1livNvGvutr4Sl6bxEs,1067
58
- agrobr-0.1.2.dist-info/RECORD,,
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