prismadata 0.7.0__tar.gz → 0.8.0__tar.gz

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 (23) hide show
  1. {prismadata-0.7.0 → prismadata-0.8.0}/PKG-INFO +1 -1
  2. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/__init__.py +10 -0
  3. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_constants.py +3 -0
  4. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_types.py +87 -0
  5. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_validation.py +41 -0
  6. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/async_client.py +158 -0
  7. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/client.py +164 -0
  8. {prismadata-0.7.0 → prismadata-0.8.0}/pyproject.toml +1 -1
  9. {prismadata-0.7.0 → prismadata-0.8.0}/LICENSE +0 -0
  10. {prismadata-0.7.0 → prismadata-0.8.0}/README.md +0 -0
  11. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_async_auth.py +0 -0
  12. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_async_http.py +0 -0
  13. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_auth.py +0 -0
  14. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_batch.py +0 -0
  15. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_cache.py +0 -0
  16. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_columns.py +0 -0
  17. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_enrich.py +0 -0
  18. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_http.py +0 -0
  19. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_prepare.py +0 -0
  20. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/_progress.py +0 -0
  21. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/exceptions.py +0 -0
  22. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/py.typed +0 -0
  23. {prismadata-0.7.0 → prismadata-0.8.0}/prismadata/sklearn.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prismadata
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Python client for the PrismaData location intelligence API
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -17,6 +17,11 @@ Async usage::
17
17
 
18
18
  from ._types import (
19
19
  BorderResult,
20
+ CnpjEnrichedResult,
21
+ CnpjMetadataRawResult,
22
+ CnpjMetadataResult,
23
+ CnpjRawResult,
24
+ CnpjResult,
20
25
  CommercialClusterResult,
21
26
  GeocodeResult,
22
27
  IncomePdfResult,
@@ -77,6 +82,11 @@ __all__ = [
77
82
  "RateLimitError",
78
83
  "ValidationError",
79
84
  "BorderResult",
85
+ "CnpjEnrichedResult",
86
+ "CnpjMetadataRawResult",
87
+ "CnpjMetadataResult",
88
+ "CnpjRawResult",
89
+ "CnpjResult",
80
90
  "CommercialClusterResult",
81
91
  "GeocodeResult",
82
92
  "IncomePdfResult",
@@ -5,6 +5,9 @@ BASE_URL = "https://api.prismadata.io/v1"
5
5
  MAX_BATCH_SIZE = 1024
6
6
  MAX_ROUTING_BATCH = 16
7
7
 
8
+ MAX_CNPJ_BATCH_FLAT = 1024
9
+ MAX_CNPJ_BATCH_RAW = 256
10
+
8
11
  DEFAULT_TIMEOUT = 30
9
12
  TOKEN_RENEW_MARGIN = 60
10
13
 
@@ -138,6 +138,93 @@ class QuotaInfo(TypedDict, total=False):
138
138
  reset: int
139
139
 
140
140
 
141
+ class CnpjResult(TypedDict, total=False):
142
+ """Response from ``cnpj()`` — flat shape (49 scalar keys).
143
+
144
+ Full field list at https://docs.prismadata.io/cnpj. Categoria prefix is
145
+ ``pessoa_juridica`` (to avoid ``prismadata__cnpj__cnpj`` collisions).
146
+ """
147
+ prismadata__pessoa_juridica__cnpj: str
148
+ prismadata__pessoa_juridica__cnpj_basico: str
149
+ prismadata__pessoa_juridica__razao_social: str
150
+ prismadata__pessoa_juridica__nome_fantasia: str
151
+ prismadata__pessoa_juridica__situacao_cadastral: str
152
+ prismadata__pessoa_juridica__data_abertura: str
153
+ prismadata__pessoa_juridica__natureza_juridica_codigo: str
154
+ prismadata__pessoa_juridica__natureza_juridica_descricao: str
155
+ prismadata__pessoa_juridica__porte: str
156
+ prismadata__pessoa_juridica__capital_social: str
157
+ prismadata__pessoa_juridica__cnae_principal_codigo: str
158
+ prismadata__pessoa_juridica__cnae_principal_descricao: str
159
+ prismadata__pessoa_juridica__cnae_secundario_1: str
160
+ prismadata__pessoa_juridica__cnae_secundario_2: str
161
+ prismadata__pessoa_juridica__cnae_secundario_3: str
162
+ prismadata__pessoa_juridica__total_cnaes_secundarios: int
163
+ prismadata__pessoa_juridica__logradouro: str
164
+ prismadata__pessoa_juridica__numero: str
165
+ prismadata__pessoa_juridica__bairro: str
166
+ prismadata__pessoa_juridica__municipio_nome: str
167
+ prismadata__pessoa_juridica__uf: str
168
+ prismadata__pessoa_juridica__cep: str
169
+ prismadata__pessoa_juridica__latitude: str
170
+ prismadata__pessoa_juridica__longitude: str
171
+ prismadata__pessoa_juridica__qualidade_geocodificacao_score: int
172
+ prismadata__pessoa_juridica__qualidade_geocodificacao_classificacao: str
173
+ prismadata__pessoa_juridica__total_socios: int
174
+ prismadata__pessoa_juridica__total_socios_pf: int
175
+ prismadata__pessoa_juridica__total_socios_pj: int
176
+ prismadata__pessoa_juridica__data_publicacao_rfb: str
177
+ prismadata__pessoa_juridica__data_processamento: str
178
+
179
+
180
+ class CnpjRawResult(TypedDict, total=False):
181
+ """Response from ``cnpj_raw()`` — F1 schema with nested ``socios`` list.
182
+
183
+ See https://docs.prismadata.io/cnpj.
184
+ """
185
+ prismadata__pessoa_juridica__cnpj: str
186
+ prismadata__pessoa_juridica__razao_social: str
187
+ prismadata__pessoa_juridica__cnaes_secundarios: list[str]
188
+ prismadata__pessoa_juridica__socios: list[dict[str, Any]]
189
+
190
+
191
+ class CnpjMetadataResult(TypedDict, total=False):
192
+ """Response from ``cnpj_metadata()`` — flat coverage buckets.
193
+
194
+ See https://docs.prismadata.io/cnpj.
195
+ """
196
+ prismadata__pessoa_juridica__versao_dados: str
197
+ prismadata__pessoa_juridica__data_publicacao_rfb: str
198
+ prismadata__pessoa_juridica__data_processamento: str
199
+ prismadata__pessoa_juridica__total_cnpjs_carregados: int
200
+ prismadata__pessoa_juridica__cobertura_geo_muito_alta: int
201
+ prismadata__pessoa_juridica__cobertura_geo_alta: int
202
+ prismadata__pessoa_juridica__cobertura_geo_media: int
203
+ prismadata__pessoa_juridica__cobertura_geo_baixa: int
204
+ prismadata__pessoa_juridica__cobertura_geo_muito_baixa: int
205
+ prismadata__pessoa_juridica__cobertura_geo_falha: int
206
+ prismadata__pessoa_juridica__cobertura_geo_sem_geo: int
207
+
208
+
209
+ class CnpjMetadataRawResult(TypedDict, total=False):
210
+ """Response from ``cnpj_raw_metadata()`` — nested coverage dict."""
211
+ prismadata__pessoa_juridica__versao_dados: str
212
+ prismadata__pessoa_juridica__data_publicacao_rfb: str
213
+ prismadata__pessoa_juridica__data_processamento: str
214
+ prismadata__pessoa_juridica__total_cnpjs_carregados: int
215
+ prismadata__pessoa_juridica__cobertura_geo: dict[str, int]
216
+
217
+
218
+ class CnpjEnrichedResult(CnpjResult, total=False):
219
+ """Response from ``cnpj_enriched()`` / values of ``cnpj_batch_enriched()``.
220
+
221
+ Extends :class:`CnpjResult` with keys from each active geo flag
222
+ (``prismadata__slum__*``, ``prismadata__border__*`` etc.). When the CNPJ
223
+ has no lat/lng the geo pillars are silently skipped — only the 49
224
+ ``pessoa_juridica`` keys are returned. See https://docs.prismadata.io/cnpj.
225
+ """
226
+
227
+
141
228
  class TipologiaResult(TypedDict, total=False):
142
229
  """Response from ``tipologia()`` (coordinate-based lookup).
143
230
 
@@ -2,10 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from typing import Any
6
7
 
7
8
  _VALID_PROFILES = {"car", "foot", "bike"}
8
9
 
10
+ _CNPJ_NON_ALNUM = re.compile(r"[^A-Za-z0-9]")
11
+ _CNPJ_BODY_PATTERN = re.compile(r"^[A-Z0-9]{12}$")
12
+ _CNPJ_DV_WEIGHTS_1 = (5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)
13
+ _CNPJ_DV_WEIGHTS_2 = (6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)
14
+
9
15
  _RANKING_SCOPE = {"national": "nacional", "state": "uf", "city": "municipio"}
10
16
  _RANKING_CRITERION = {"score": "score", "pdvs": "pdvs", "density": "densidade"}
11
17
  _SEARCH_FILTER_KEYS = {
@@ -46,6 +52,41 @@ def validate_route_points(points: list) -> None:
46
52
  )
47
53
 
48
54
 
55
+ def _cnpj_char_value(c: str) -> int:
56
+ return ord(c) - ord("0")
57
+
58
+
59
+ def _cnpj_dv(body: str, weights: tuple[int, ...]) -> str:
60
+ total = sum(_cnpj_char_value(c) * w for c, w in zip(body, weights))
61
+ resto = total % 11
62
+ return "0" if resto < 2 else str(11 - resto)
63
+
64
+
65
+ def validate_cnpj(cnpj: str) -> str:
66
+ """Normalize and validate a CNPJ (supports alphanumeric NT COCAD/SUARA 49/2024).
67
+
68
+ Accepts masked input (e.g. ``12.345.678/0001-95``) and lowercase letters.
69
+ Returns the normalized 14-char CNPJ. Raises ``ValueError`` if invalid.
70
+ """
71
+ if not isinstance(cnpj, str):
72
+ raise ValueError(f"cnpj must be a string, got {type(cnpj).__name__}")
73
+ normalized = _CNPJ_NON_ALNUM.sub("", cnpj).upper()
74
+ if len(normalized) != 14:
75
+ raise ValueError(f"cnpj must have 14 alphanumeric chars, got {len(normalized)}")
76
+ if len(set(normalized)) == 1:
77
+ raise ValueError("cnpj cannot have all identical characters")
78
+ body, dv = normalized[:12], normalized[12:]
79
+ if not _CNPJ_BODY_PATTERN.match(body):
80
+ raise ValueError("cnpj body (positions 1-12) must be A-Z or 0-9")
81
+ if not dv.isdigit():
82
+ raise ValueError("cnpj check digits (positions 13-14) must be digits")
83
+ if _cnpj_dv(body, _CNPJ_DV_WEIGHTS_1) != dv[0]:
84
+ raise ValueError("cnpj first check digit is invalid")
85
+ if _cnpj_dv(body + dv[0], _CNPJ_DV_WEIGHTS_2) != dv[1]:
86
+ raise ValueError("cnpj second check digit is invalid")
87
+ return normalized
88
+
89
+
49
90
  def validate_address_fields(**fields: Any) -> None:
50
91
  """Raise ValueError if no address field is provided."""
51
92
  if not any(v is not None for v in fields.values()):
@@ -19,6 +19,8 @@ from ._constants import (
19
19
  DEFAULT_TIMEOUT,
20
20
  DIRECTION_MAP,
21
21
  MAX_BATCH_SIZE,
22
+ MAX_CNPJ_BATCH_FLAT,
23
+ MAX_CNPJ_BATCH_RAW,
22
24
  MAX_ROUTING_BATCH,
23
25
  )
24
26
  from ._prepare import async_batch_complete, async_batch_prepare, async_wait_until_ready
@@ -28,6 +30,7 @@ from ._validation import (
28
30
  translate_ranking_scope,
29
31
  translate_search_filters,
30
32
  validate_address_fields,
33
+ validate_cnpj,
31
34
  validate_lat_lng,
32
35
  validate_profile,
33
36
  validate_route_points,
@@ -39,6 +42,11 @@ if TYPE_CHECKING:
39
42
 
40
43
  from ._types import (
41
44
  BorderResult,
45
+ CnpjEnrichedResult,
46
+ CnpjMetadataRawResult,
47
+ CnpjMetadataResult,
48
+ CnpjRawResult,
49
+ CnpjResult,
42
50
  CommercialClusterResult,
43
51
  GeocodeResult,
44
52
  IncomePdfResult,
@@ -717,6 +725,156 @@ class AsyncClient:
717
725
  return [clean_columns(item) for item in items]
718
726
  return items
719
727
 
728
+ # ------------------------------------------------------------------
729
+ # CNPJ (pessoa juridica)
730
+ # ------------------------------------------------------------------
731
+
732
+ async def cnpj(self, cnpj: str) -> CnpjResult:
733
+ """Individual CNPJ lookup, flat shape.
734
+
735
+ See :meth:`Client.cnpj` for full docs.
736
+ """
737
+ normalized = validate_cnpj(cnpj)
738
+ return self._maybe_clean(await self._get(f"/cnpj/{normalized}"))
739
+
740
+ async def cnpj_raw(self, cnpj: str) -> CnpjRawResult:
741
+ """Individual CNPJ lookup with F1 schema.
742
+
743
+ See :meth:`Client.cnpj_raw` for full docs.
744
+ """
745
+ normalized = validate_cnpj(cnpj)
746
+ return self._maybe_clean(await self._get(f"/cnpj/raw/{normalized}"))
747
+
748
+ async def cnpj_batch(
749
+ self,
750
+ cnpjs: list[str],
751
+ *,
752
+ timeout: int | None = None,
753
+ ) -> dict[str, CnpjResult | None]:
754
+ """Batch CNPJ lookup, flat shape. Server caps at 1024.
755
+
756
+ See :meth:`Client.cnpj_batch` for full docs.
757
+ """
758
+ if len(cnpjs) > MAX_CNPJ_BATCH_FLAT:
759
+ raise ValueError(
760
+ f"Batch limit is {MAX_CNPJ_BATCH_FLAT} CNPJs per call, got {len(cnpjs)}"
761
+ )
762
+ normalized = [validate_cnpj(c) for c in cnpjs]
763
+ result = await self._post("/cnpj/batch", {"cnpjs": normalized}, timeout=timeout)
764
+ if self._clean:
765
+ return {k: clean_columns(v) if isinstance(v, dict) else v for k, v in result.items()}
766
+ return result
767
+
768
+ async def cnpj_raw_batch(
769
+ self,
770
+ cnpjs: list[str],
771
+ *,
772
+ timeout: int | None = None,
773
+ ) -> dict[str, CnpjRawResult | None]:
774
+ """Batch CNPJ lookup with F1 schema. Server caps at 256.
775
+
776
+ See :meth:`Client.cnpj_raw_batch` for full docs.
777
+ """
778
+ if len(cnpjs) > MAX_CNPJ_BATCH_RAW:
779
+ raise ValueError(
780
+ f"Batch limit is {MAX_CNPJ_BATCH_RAW} CNPJs per call, got {len(cnpjs)}"
781
+ )
782
+ normalized = [validate_cnpj(c) for c in cnpjs]
783
+ result = await self._post("/cnpj/raw/batch", {"cnpjs": normalized}, timeout=timeout)
784
+ if self._clean:
785
+ return {k: clean_columns(v) if isinstance(v, dict) else v for k, v in result.items()}
786
+ return result
787
+
788
+ async def cnpj_metadata(self) -> CnpjMetadataResult:
789
+ """Dataset metadata (flat).
790
+
791
+ See :meth:`Client.cnpj_metadata` for full docs.
792
+ """
793
+ return self._maybe_clean(await self._get("/cnpj/metadata"))
794
+
795
+ async def cnpj_raw_metadata(self) -> CnpjMetadataRawResult:
796
+ """Dataset metadata with ``cobertura_geo`` as nested dict.
797
+
798
+ See :meth:`Client.cnpj_raw_metadata` for full docs.
799
+ """
800
+ return self._maybe_clean(await self._get("/cnpj/raw/metadata"))
801
+
802
+ async def cnpj_enriched(
803
+ self,
804
+ cnpj: str,
805
+ *,
806
+ slum: bool = False,
807
+ prison: bool = False,
808
+ border: bool = False,
809
+ personal_income_static: bool = False,
810
+ personal_income_pdf: bool = False,
811
+ commercial_cluster: bool = False,
812
+ infosc: bool = False,
813
+ tipologia: bool = False,
814
+ sandbox: bool = False,
815
+ ) -> CnpjEnrichedResult:
816
+ """CNPJ lookup with geo fan-out.
817
+
818
+ See :meth:`Client.cnpj_enriched` for full docs.
819
+ """
820
+ normalized = validate_cnpj(cnpj)
821
+ flags = {
822
+ "slum": slum,
823
+ "prison": prison,
824
+ "border": border,
825
+ "personal_income_static": personal_income_static,
826
+ "personal_income_pdf": personal_income_pdf,
827
+ "commercial_cluster": commercial_cluster,
828
+ "infosc": infosc,
829
+ "tipologia": tipologia,
830
+ "sandbox": sandbox,
831
+ }
832
+ params = {k: "true" for k, v in flags.items() if v}
833
+ return self._maybe_clean(
834
+ await self._get(f"/cnpj/{normalized}/enriched", params)
835
+ )
836
+
837
+ async def cnpj_batch_enriched(
838
+ self,
839
+ cnpjs: list[str],
840
+ *,
841
+ slum: bool = False,
842
+ prison: bool = False,
843
+ border: bool = False,
844
+ personal_income_static: bool = False,
845
+ personal_income_pdf: bool = False,
846
+ commercial_cluster: bool = False,
847
+ infosc: bool = False,
848
+ tipologia: bool = False,
849
+ timeout: int | None = None,
850
+ ) -> dict[str, CnpjEnrichedResult | None]:
851
+ """Batch CNPJ lookup with geo fan-out. Server caps at 256.
852
+
853
+ See :meth:`Client.cnpj_batch_enriched` for full docs.
854
+ """
855
+ if len(cnpjs) > MAX_CNPJ_BATCH_RAW:
856
+ raise ValueError(
857
+ f"Batch limit is {MAX_CNPJ_BATCH_RAW} CNPJs per call, got {len(cnpjs)}"
858
+ )
859
+ normalized = [validate_cnpj(c) for c in cnpjs]
860
+ body = {
861
+ "cnpjs": normalized,
862
+ "flags": {
863
+ "slum": slum,
864
+ "prison": prison,
865
+ "border": border,
866
+ "personal_income_static": personal_income_static,
867
+ "personal_income_pdf": personal_income_pdf,
868
+ "commercial_cluster": commercial_cluster,
869
+ "infosc": infosc,
870
+ "tipologia": tipologia,
871
+ },
872
+ }
873
+ result = await self._post("/cnpj/batch/enriched", body, timeout=timeout)
874
+ if self._clean:
875
+ return {k: clean_columns(v) if isinstance(v, dict) else v for k, v in result.items()}
876
+ return result
877
+
720
878
  # ------------------------------------------------------------------
721
879
  # Batch operations
722
880
  # ------------------------------------------------------------------
@@ -22,6 +22,8 @@ from ._constants import (
22
22
  ENV_PASSWORD,
23
23
  ENV_USERNAME,
24
24
  MAX_BATCH_SIZE,
25
+ MAX_CNPJ_BATCH_FLAT,
26
+ MAX_CNPJ_BATCH_RAW,
25
27
  MAX_ROUTING_BATCH,
26
28
  )
27
29
  from ._http import HttpClient, _handle_response
@@ -32,6 +34,7 @@ from ._validation import (
32
34
  translate_ranking_scope,
33
35
  translate_search_filters,
34
36
  validate_address_fields,
37
+ validate_cnpj,
35
38
  validate_lat_lng,
36
39
  validate_profile,
37
40
  validate_route_points,
@@ -43,6 +46,11 @@ if TYPE_CHECKING:
43
46
 
44
47
  from ._types import (
45
48
  BorderResult,
49
+ CnpjEnrichedResult,
50
+ CnpjMetadataRawResult,
51
+ CnpjMetadataResult,
52
+ CnpjRawResult,
53
+ CnpjResult,
46
54
  CommercialClusterResult,
47
55
  GeocodeResult,
48
56
  IncomePdfResult,
@@ -923,6 +931,162 @@ class Client:
923
931
  return [clean_columns(item) for item in items]
924
932
  return items
925
933
 
934
+ # ------------------------------------------------------------------
935
+ # CNPJ (pessoa juridica)
936
+ # ------------------------------------------------------------------
937
+
938
+ def cnpj(self, cnpj: str) -> CnpjResult:
939
+ """Individual CNPJ lookup, flat shape (49 scalar keys, no nested lists).
940
+
941
+ Accepts masked or alphanumeric input; the SDK normalizes and validates
942
+ the check digits locally before hitting the server.
943
+ See https://docs.prismadata.io/cnpj.
944
+ """
945
+ normalized = validate_cnpj(cnpj)
946
+ return self._maybe_clean(self._get(f"/cnpj/{normalized}"))
947
+
948
+ def cnpj_raw(self, cnpj: str) -> CnpjRawResult:
949
+ """Individual CNPJ lookup with F1 schema (``socios`` and ``cnaes`` as lists).
950
+
951
+ See https://docs.prismadata.io/cnpj.
952
+ """
953
+ normalized = validate_cnpj(cnpj)
954
+ return self._maybe_clean(self._get(f"/cnpj/raw/{normalized}"))
955
+
956
+ def cnpj_batch(
957
+ self,
958
+ cnpjs: list[str],
959
+ *,
960
+ timeout: int | None = None,
961
+ ) -> dict[str, CnpjResult | None]:
962
+ """Batch CNPJ lookup, flat shape. Server caps at 1024 per call.
963
+
964
+ Returns a dict indexed by the normalized CNPJ, with ``None`` for
965
+ CNPJs not found by the server. See https://docs.prismadata.io/cnpj.
966
+ """
967
+ if len(cnpjs) > MAX_CNPJ_BATCH_FLAT:
968
+ raise ValueError(
969
+ f"Batch limit is {MAX_CNPJ_BATCH_FLAT} CNPJs per call, got {len(cnpjs)}"
970
+ )
971
+ normalized = [validate_cnpj(c) for c in cnpjs]
972
+ result = self._post("/cnpj/batch", {"cnpjs": normalized}, timeout=timeout)
973
+ if self._clean:
974
+ return {k: clean_columns(v) if isinstance(v, dict) else v for k, v in result.items()}
975
+ return result
976
+
977
+ def cnpj_raw_batch(
978
+ self,
979
+ cnpjs: list[str],
980
+ *,
981
+ timeout: int | None = None,
982
+ ) -> dict[str, CnpjRawResult | None]:
983
+ """Batch CNPJ lookup with F1 schema. Server caps at 256 per call
984
+ (raw payload is larger than flat).
985
+
986
+ See https://docs.prismadata.io/cnpj.
987
+ """
988
+ if len(cnpjs) > MAX_CNPJ_BATCH_RAW:
989
+ raise ValueError(
990
+ f"Batch limit is {MAX_CNPJ_BATCH_RAW} CNPJs per call, got {len(cnpjs)}"
991
+ )
992
+ normalized = [validate_cnpj(c) for c in cnpjs]
993
+ result = self._post("/cnpj/raw/batch", {"cnpjs": normalized}, timeout=timeout)
994
+ if self._clean:
995
+ return {k: clean_columns(v) if isinstance(v, dict) else v for k, v in result.items()}
996
+ return result
997
+
998
+ def cnpj_metadata(self) -> CnpjMetadataResult:
999
+ """Dataset metadata (version + geo coverage buckets, flat).
1000
+
1001
+ See https://docs.prismadata.io/cnpj.
1002
+ """
1003
+ return self._maybe_clean(self._get("/cnpj/metadata"))
1004
+
1005
+ def cnpj_raw_metadata(self) -> CnpjMetadataRawResult:
1006
+ """Dataset metadata with ``cobertura_geo`` as a nested dict.
1007
+
1008
+ See https://docs.prismadata.io/cnpj.
1009
+ """
1010
+ return self._maybe_clean(self._get("/cnpj/raw/metadata"))
1011
+
1012
+ def cnpj_enriched(
1013
+ self,
1014
+ cnpj: str,
1015
+ *,
1016
+ slum: bool = False,
1017
+ prison: bool = False,
1018
+ border: bool = False,
1019
+ personal_income_static: bool = False,
1020
+ personal_income_pdf: bool = False,
1021
+ commercial_cluster: bool = False,
1022
+ infosc: bool = False,
1023
+ tipologia: bool = False,
1024
+ sandbox: bool = False,
1025
+ ) -> CnpjEnrichedResult:
1026
+ """CNPJ lookup with optional fan-out to geo pillars.
1027
+
1028
+ When a flag is set, the server appends ``prismadata__<flag>__*`` keys
1029
+ to the flat response. When the CNPJ has no lat/lng, geo flags are
1030
+ silently skipped and only the 49 ``pessoa_juridica`` keys come back
1031
+ (``qualidade_geocodificacao_classificacao`` will be ``"FALHA"`` or
1032
+ ``null``). See https://docs.prismadata.io/cnpj.
1033
+ """
1034
+ normalized = validate_cnpj(cnpj)
1035
+ flags = {
1036
+ "slum": slum,
1037
+ "prison": prison,
1038
+ "border": border,
1039
+ "personal_income_static": personal_income_static,
1040
+ "personal_income_pdf": personal_income_pdf,
1041
+ "commercial_cluster": commercial_cluster,
1042
+ "infosc": infosc,
1043
+ "tipologia": tipologia,
1044
+ "sandbox": sandbox,
1045
+ }
1046
+ params = {k: "true" for k, v in flags.items() if v}
1047
+ return self._maybe_clean(self._get(f"/cnpj/{normalized}/enriched", params))
1048
+
1049
+ def cnpj_batch_enriched(
1050
+ self,
1051
+ cnpjs: list[str],
1052
+ *,
1053
+ slum: bool = False,
1054
+ prison: bool = False,
1055
+ border: bool = False,
1056
+ personal_income_static: bool = False,
1057
+ personal_income_pdf: bool = False,
1058
+ commercial_cluster: bool = False,
1059
+ infosc: bool = False,
1060
+ tipologia: bool = False,
1061
+ timeout: int | None = None,
1062
+ ) -> dict[str, CnpjEnrichedResult | None]:
1063
+ """Batch CNPJ lookup with optional geo fan-out. Server caps at 256 per call.
1064
+
1065
+ See https://docs.prismadata.io/cnpj.
1066
+ """
1067
+ if len(cnpjs) > MAX_CNPJ_BATCH_RAW:
1068
+ raise ValueError(
1069
+ f"Batch limit is {MAX_CNPJ_BATCH_RAW} CNPJs per call, got {len(cnpjs)}"
1070
+ )
1071
+ normalized = [validate_cnpj(c) for c in cnpjs]
1072
+ body = {
1073
+ "cnpjs": normalized,
1074
+ "flags": {
1075
+ "slum": slum,
1076
+ "prison": prison,
1077
+ "border": border,
1078
+ "personal_income_static": personal_income_static,
1079
+ "personal_income_pdf": personal_income_pdf,
1080
+ "commercial_cluster": commercial_cluster,
1081
+ "infosc": infosc,
1082
+ "tipologia": tipologia,
1083
+ },
1084
+ }
1085
+ result = self._post("/cnpj/batch/enriched", body, timeout=timeout)
1086
+ if self._clean:
1087
+ return {k: clean_columns(v) if isinstance(v, dict) else v for k, v in result.items()}
1088
+ return result
1089
+
926
1090
  # ------------------------------------------------------------------
927
1091
  # Batch operations
928
1092
  # ------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "prismadata"
3
- version = "0.7.0"
3
+ version = "0.8.0"
4
4
  description = "Python client for the PrismaData location intelligence API"
5
5
  authors = ["PrismaData <contato@prismadata.io>"]
6
6
  license = "MIT"
File without changes
File without changes