Commit
[ka2] fix connecteur gestion_isr: la table listings passée derrière RLS — lecture dynamique de LISTINGS_URL (vue listings_public) + retry anti-timeout. Depuis le run du 2026-10-20 15:03 le connecteur rapportait found=0 « ok » (alerte dérive, retraits suspendus) : le portail location.gestion-isr.com a migré ses annonces publiques vers la VUE expurgée /rest/v1/listings_public (migration 145-listings-vue-publique.sql visible dans le JS inline) et l'ancienne table /rest/v1/listings renvoie désormais 200 [] à la clé anon (RLS) — zéro silencieux classique. Fix : le connecteur lit maintenant le chemin REST dans la const LISTINGS_URL du HTML du portail (même philosophie que l'URL/clé Supabase déjà lues dynamiquement — se répare seul au prochain renommage), erreur FRANCHE si la const disparaît. Piège rencontré en route : la vue est coûteuse côté Postgres — « canceling statement due to statement timeout » (57014 → 500) sur 2 runs sur 4 → retry ×3 (pause 4 s/8 s, pattern solaris 8eff094), même URL (timeout serveur, pas un cache). Schéma de la vue identique à l'ancienne table pour tous les champs consommés (pk, id_app, titre, id, loyer, adresse_geo, units, photos…) : _parse_row inchangé, external_id stables. Fixture ré-enregistrée (2 requêtes, 20 annonces), pytest gestion_isr 2 passed. Testé : fetch() venv → 20 annonces (médiane 20 ; 21 lignes dans la vue, 1 exclue par les filtres), run.py sync gestion_isr → found=20 ok (updated=1, 2,5 s), pm2 restart lou-ka-sync, site :8095 → 200.
modified
louka/connectors/gestion_isr.py
+34 −8
@@ -3,15 +3,18 @@ | ||
| 3 | 3 | # Auteur : Simon-Pierre Boucher — contact@spboucher.ai |
| 4 | 4 | # connectors/gestion_isr.py : connecteur Gestion ISR (gestion-isr.com) |
| 5 | 5 | # Le site vitrine (SPA Vite/React) renvoie vers le portail public |
| 6 | −# location.gestion-isr.com, propulsé par Supabase. On lit l'URL et la clé | |
| 7 | −# anonyme publiques embarquées dans le HTML du portail, puis l'API REST | |
| 8 | −# /rest/v1/listings?statut=eq.Actif livre tout le parc actif en JSON | |
| 9 | −# structuré (loyer, type, ville, GPS, photos, unités par immeuble). | |
| 6 | +# location.gestion-isr.com, propulsé par Supabase. On lit l'URL, la clé | |
| 7 | +# anonyme ET le chemin REST des annonces (const LISTINGS_URL) embarqués dans | |
| 8 | +# le HTML du portail — depuis 2026-10 c'est la vue publique expurgée | |
| 9 | +# /rest/v1/listings_public?statut=eq.Actif (la table listings est passée | |
| 10 | +# derrière RLS et renvoie 200 []). JSON structuré (loyer, type, ville, GPS, | |
| 11 | +# photos, unités par immeuble). | |
| 10 | 12 | # 2 requêtes par sync ; lien profond ?fiche=<id_app> par annonce. |
| 11 | 13 | # ----------------------------------------------------------------------------- |
| 12 | 14 | from __future__ import annotations |
| 13 | 15 | |
| 14 | 16 | import re |
| 17 | +import time | |
| 15 | 18 | |
| 16 | 19 | from ..schema import Listing, normalize_unit_type, strip_accents |
| 17 | 20 | from .base import BaseConnector |
@@ -20,6 +23,9 @@ PORTAL = "https://location.gestion-isr.com/" | ||
| 20 | 23 | |
| 21 | 24 | _URL_RE = re.compile(r"SUPABASE_URL\s*=\s*'([^']+)'") |
| 22 | 25 | _KEY_RE = re.compile(r"SUPABASE_ANON\s*=\s*'([^']+)'") |
| 26 | +# Chemin REST que le portail requête lui-même (vue publique) — suivi | |
| 27 | +# dynamiquement pour survivre aux renommages de table/vue côté Supabase. | |
| 28 | +_LIST_RE = re.compile(r"LISTINGS_URL\s*=\s*`\$\{SUPABASE_URL\}([^`]+)`") | |
| 23 | 29 | |
| 24 | 30 | |
| 25 | 31 | def _pets_value(raw: str) -> str | None: |
@@ -48,10 +54,30 @@ class GestionIsrConnector(BaseConnector): | ||
| 48 | 54 | raise RuntimeError("clé/URL Supabase introuvables dans le portail") |
| 49 | 55 | base, key = m_url.group(1).rstrip("/"), m_key.group(1) |
| 50 | 56 | |
| 51 | − rows = self.get( | |
| 52 | − f"{base}/rest/v1/listings?select=*&statut=eq.Actif", | |
| 53 | − headers={"apikey": key, "Authorization": f"Bearer {key}"}, | |
| 54 | − ).json() | |
| 57 | + m_list = _LIST_RE.search(html) | |
| 58 | + if not m_list: | |
| 59 | + # erreur FRANCHE plutôt qu'un found=0 silencieux (cf. 2026-10-20 : | |
| 60 | + # listings → listings_public, l'ancienne table renvoyait 200 []) | |
| 61 | + raise RuntimeError("const LISTINGS_URL introuvable dans le portail") | |
| 62 | + | |
| 63 | + # La vue listings_public est coûteuse côté Postgres : « canceling | |
| 64 | + # statement due to statement timeout » (57014 → 500) observé 2 runs | |
| 65 | + # sur 4 le 2026-10-20. Retry avant l'erreur franche (pattern solaris | |
| 66 | + # 8eff094) ; même URL — timeout serveur, pas un problème de cache. | |
| 67 | + last_err: Exception | None = None | |
| 68 | + for attempt in range(3): | |
| 69 | + if attempt: | |
| 70 | + time.sleep(4 * attempt) | |
| 71 | + try: | |
| 72 | + rows = self.get( | |
| 73 | + base + m_list.group(1), | |
| 74 | + headers={"apikey": key, "Authorization": f"Bearer {key}"}, | |
| 75 | + ).json() | |
| 76 | + break | |
| 77 | + except Exception as err: | |
| 78 | + last_err = err | |
| 79 | + else: | |
| 80 | + raise last_err | |
| 55 | 81 | |
| 56 | 82 | listings: list[Listing] = [] |
| 57 | 83 | for row in rows: |
modified
tests/fixtures/gestion_isr/b04f0e8896bb41e1dffc.html
+1638 −233
Large diff (2,563 lines) is not loaded by default to keep the page fast.Load diff
added
tests/fixtures/gestion_isr/c4ec1dc08a7fc775fe37.json
+21 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
deleted
tests/fixtures/gestion_isr/cdc93d52342c23a37e76.json
+0 −21
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
modified
tests/fixtures/gestion_isr/expected.json
+68 −82
Large diff (243 lines) is not loaded by default to keep the page fast.Load diff
modified
tests/fixtures/gestion_isr/index.json
+3 −3
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff