permisapi-client 0.7.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: permisapi-client
3
- Version: 0.7.1
3
+ Version: 0.8.0
4
4
  Summary: Client Python officiel pour PermisAPI : permis de construire France en API REST.
5
5
  Project-URL: Homepage, https://permisapi.fr
6
6
  Project-URL: Documentation, https://permisapi.fr/docs
@@ -30,4 +30,4 @@ __all__ = [
30
30
  "PermisAPIValidationError",
31
31
  ]
32
32
 
33
- __version__ = "0.7.1"
33
+ __version__ = "0.8.0"
@@ -13,7 +13,7 @@ from permisapi_client.exceptions import error_from_response
13
13
 
14
14
  DEFAULT_BASE_URL = "https://api.permisapi.fr"
15
15
  DEFAULT_TIMEOUT = 15.0
16
- USER_AGENT = "permisapi-client-python/0.7.1"
16
+ USER_AGENT = "permisapi-client-python/0.8.0"
17
17
 
18
18
 
19
19
  def _raise_for_status(resp: httpx.Response) -> None:
@@ -63,6 +63,32 @@ class _PermitsResource:
63
63
  Champs autorises : date_reelle_autorisation, date_depot,
64
64
  an_depot, superficie_terrain. Prefixe `-` pour descendant.
65
65
  Tout autre champ retourne 422 (et non ignore silencieusement).
66
+
67
+ Filtre `min_score` (Pro+ uniquement) : retourne uniquement les
68
+ permis avec un Score Opportunite MDB >= N (0-100). Use case
69
+ marchand de biens : shortliste les top opportunites en 1 call
70
+ au lieu de fetch + filter cote client. Cout quota special :
71
+ `limit` unites au lieu de 1 (anti-abus, equivalent a N calls
72
+ /score). Header X-RateLimit-Cost confirme.
73
+
74
+ Filtre `max_risk` (Pro+ uniquement) : exclut les permis dont
75
+ la commune est classee au-dessus du tier specifie (low /
76
+ moderate / high / critical). Exemple `max_risk="moderate"`
77
+ retourne uniquement les permis low + moderate. Cout quota =
78
+ limit (anti-abus, equivalent a N calls /risks). Lookup via
79
+ colonne materialisee `permits.risk_tier` (refresh par
80
+ scripts/materialize_risks.py mensuel).
81
+
82
+ Filtre `plu_zone_type` (Pro+ uniquement) : prefix de zonage
83
+ urbanisme parmi U / AU / A / N / OTHER. Exemple
84
+ `plu_zone_type="U"` retourne uniquement les permis en zone
85
+ urbaine constructible. Cout quota = limit.
86
+
87
+ Filtre `plu_constructible` (Pro+ uniquement) : booleen.
88
+ `plu_constructible=True` retourne uniquement les permis ou le
89
+ verdict heuristique est constructible (zone U ou AU). Pratique
90
+ combine avec `min_score` pour shortlister les vraies
91
+ opportunites MDB. Cout quota = limit.
66
92
  """
67
93
  return self._c._get("/v1/permits", params=params)
68
94
 
@@ -101,6 +127,24 @@ class _PermitsResource:
101
127
  params={"section": section, "numero": numero},
102
128
  )
103
129
 
130
+ def parcelle(self, num_pa: str) -> dict[str, Any]:
131
+ """GET /v1/permits/{num_pa}/parcelle : geometrie cadastre DGFiP.
132
+
133
+ Plans Pro et superieurs uniquement (402 si Free / Explorer).
134
+ Retourne {num_pa, comm_code, section, numero, cadastre_id,
135
+ surface_m2 (contenance officielle DGFiP), geometry (GeoJSON
136
+ Polygon WGS84), cached_at, source}.
137
+
138
+ 404 si la parcelle n'est pas encore en cache (commune hors
139
+ couverture Etalab ou pas encore materialise par
140
+ scripts/ingest_cadastre.py).
141
+
142
+ Use case : afficher le polygon precis sur une carte (vs juste
143
+ un point lat/lng), comparer surface DGFiP vs Sitadel, cross-ref
144
+ avec DVF en parcelle precise.
145
+ """
146
+ return self._c._get(f"/v1/permits/{num_pa}/parcelle")
147
+
104
148
  def dvf(
105
149
  self,
106
150
  num_pa: str,
@@ -430,6 +474,37 @@ class PermisAPI:
430
474
  self.alerts = _AlertsResource(self)
431
475
  self.me = _MeResource(self)
432
476
 
477
+ def search(
478
+ self,
479
+ q: str,
480
+ *,
481
+ dep_code: str | None = None,
482
+ limit: int = 20,
483
+ ) -> list[dict[str, Any]]:
484
+ """GET /v1/search : recherche fuzzy par texte sur les adresses.
485
+
486
+ Utilise pg_trgm cote DB. Insensible aux accents et a la casse,
487
+ tolerant aux typos courants. Latence ~50-200ms.
488
+
489
+ Tous plans (Free, Explorer, Pro, Business, Enterprise) avec
490
+ respect du scope geo. Cout quota = 1 unite (endpoint
491
+ d'acquisition, pas premium).
492
+
493
+ Args:
494
+ q: texte libre, min 2 chars (ex "republique bordeaux",
495
+ "rue victor hugo paris", "cours de l'ile")
496
+ dep_code: optionnel, restreindre a un departement
497
+ limit: 1-50, default 20
498
+
499
+ Returns:
500
+ Liste de PermitSummary tries par pertinence (similarity)
501
+ puis date desc.
502
+ """
503
+ params: dict[str, Any] = {"q": q, "limit": limit}
504
+ if dep_code is not None:
505
+ params["dep_code"] = dep_code
506
+ return self._get("/v1/search", params=params)
507
+
433
508
  def close(self) -> None:
434
509
  self._http.close()
435
510
 
@@ -546,6 +621,11 @@ class _AsyncPermitsResource:
546
621
  Voir _PermitsResource.risks pour la doc complete."""
547
622
  return await self._c._get(f"/v1/permits/{num_pa}/risks")
548
623
 
624
+ async def parcelle(self, num_pa: str) -> dict[str, Any]:
625
+ """GET /v1/permits/{num_pa}/parcelle : version async. Plans Pro+ only.
626
+ Voir _PermitsResource.parcelle pour la doc complete."""
627
+ return await self._c._get(f"/v1/permits/{num_pa}/parcelle")
628
+
549
629
  async def full_view(self, num_pa: str) -> dict[str, Any]:
550
630
  """GET /v1/permits/{num_pa}/360 : version async. Vue agregee 6-en-1.
551
631
 
@@ -682,6 +762,20 @@ class AsyncPermisAPI:
682
762
  self.alerts = _AsyncAlertsResource(self)
683
763
  self.me = _AsyncMeResource(self)
684
764
 
765
+ async def search(
766
+ self,
767
+ q: str,
768
+ *,
769
+ dep_code: str | None = None,
770
+ limit: int = 20,
771
+ ) -> list[dict[str, Any]]:
772
+ """GET /v1/search : version async. Voir PermisAPI.search pour
773
+ la doc complete (fuzzy pg_trgm sur full_address)."""
774
+ params: dict[str, Any] = {"q": q, "limit": limit}
775
+ if dep_code is not None:
776
+ params["dep_code"] = dep_code
777
+ return await self._get("/v1/search", params=params)
778
+
685
779
  async def close(self) -> None:
686
780
  await self._http.aclose()
687
781
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "permisapi-client"
7
- version = "0.7.1"
7
+ version = "0.8.0"
8
8
  description = "Client Python officiel pour PermisAPI : permis de construire France en API REST."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"