[ka2] fix connecteur sotramont: hoquets « 200 sans cartes » récurrents retryables sur le portfolio. Au sync de 21:18 le connecteur a échoué (stale) sur une page 200 servie SANS cartes article.c--article-project — 2e épisode du même hoquet (10-17, conclu « rien à faire » : il revient), alors que le connecteur est sain (found=8 stable, re-testé live : 200, 152 occurrences de c--article-project, fetch venv → 8 unités). Le retry du fix 09-21 ne couvrait que l'échec du GET : un 200 vide partait directement en erreur franche et tuait le sync sur un hoquet isolé. Fix minimal (patron morin ae6ac03 / groupe_robin 3f5c9dc) : la boucle de retry ×3 (backoff 0/3/6 s) couvre désormais AUSSI le cas « GET ok mais 0 carte » ; erreurs franches inchangées après 3 échecs (pas de zéro silencieux), distinction conservée entre portfolio inaccessible et gabarit remanié. Fixtures inchangées, pytest sotramont 2 passed. Testé : fetch() venv → 8 unités (Pointe-Claire 3, Mascouche 2, Repentigny 2, Saint-Laurent 1 ; 1 460-1 970 $), run.py sync sotramont → found=8 ok (added=0, updated=0, removed=0, 1,4 s) = médiane exacte 8.0, pm2 restart lou-ka-sync, site :8095 → 200.
1 changed file +21 −17
modified
louka/connectors/sotramont.py
+21 −17
@@ -52,32 +52,36 @@ class SotramontConnector(BaseConnector): | |||||
| 52 | 52 | ||||
| 53 | def fetch(self) -> list[Listing]: | 53 | def fetch(self) -> list[Listing]: | ||
| 54 | listings: list[Listing] = [] | 54 | listings: list[Listing] = [] | ||
| 55 | − # Hoquets réseau transitoires (vagues DNS du nœud 09-10/09-18, resets | 55 | + # Hoquets transitoires récurrents : GET en échec (vagues DNS du nœud | ||
| 56 | − # du VPS) : retry court, puis échec FRANC — un `return []` silencieux | 56 | + # 09-10/09-18, resets du VPS) mais AUSSI 200 servi SANS cartes | ||
| 57 | − # devenait « 0 trouvé ok » et déclenchait la dérive/stale à tort. | 57 | + # (épisodes 10-17 et 10-29) — les deux se retryent, puis échec FRANC | ||
| 58 | − html = "" | 58 | + # (jamais de `return []` silencieux → « 0 trouvé ok » à tort). | ||
| 59 | + arts = [] | ||||
| 59 | last_err: Exception | None = None | 60 | last_err: Exception | None = None | ||
| 60 | for attempt in range(3): | 61 | for attempt in range(3): | ||
| 62 | + if attempt: | ||||
| 63 | + time.sleep(3 * attempt) | ||||
| 61 | try: | 64 | try: | ||
| 62 | html = self.get(LIST_URL).text | 65 | html = self.get(LIST_URL).text | ||
| 63 | − break | ||||
| 64 | except Exception as e: # noqa: BLE001 — retry puis erreur franche | 66 | except Exception as e: # noqa: BLE001 — retry puis erreur franche | ||
| 65 | last_err = e | 67 | last_err = e | ||
| 66 | − if attempt < 2: | 68 | + continue | ||
| 67 | − time.sleep(3 * (attempt + 1)) | 69 | + soup = BeautifulSoup(html, "html.parser") | ||
| 70 | + arts = soup.select("article.c--article-project") | ||||
| 71 | + if arts: | ||||
| 72 | + break | ||||
| 73 | + last_err = None | ||||
| 68 | else: | 74 | else: | ||
| 69 | − raise RuntimeError( | 75 | + if last_err is not None: | ||
| 70 | − f"sotramont : portfolio inaccessible après 3 tentatives " | 76 | + raise RuntimeError( | ||
| 71 | − f"({last_err!r}) — {LIST_URL}") | 77 | + f"sotramont : portfolio inaccessible après 3 tentatives " | ||
| 72 | − soup = BeautifulSoup(html, "html.parser") | 78 | + f"({last_err!r}) — {LIST_URL}") | ||
| 73 | − | ||||
| 74 | − arts = soup.select("article.c--article-project") | ||||
| 75 | − if not arts: | ||||
| 76 | # Vrai zéro = cartes présentes mais aucune locative active ; | 79 | # Vrai zéro = cartes présentes mais aucune locative active ; | ||
| 77 | − # AUCUNE carte = gabarit remanié ou page vide → erreur franche. | 80 | + # AUCUNE carte ×3 = gabarit remanié ou page vide → erreur franche. | ||
| 78 | raise RuntimeError( | 81 | raise RuntimeError( | ||
| 79 | − "sotramont : aucune carte projet dans le portfolio — " | 82 | + "sotramont : aucune carte projet dans le portfolio " | ||
| 80 | − f"site remanié ? — {LIST_URL}") | 83 | + "(3 tentatives) — site remanié ? — " | ||
| 84 | + f"{LIST_URL}") | ||||
| 81 | 85 | ||||
| 82 | seen: set[str] = set() | 86 | seen: set[str] = set() | ||
| 83 | for art in arts: | 87 | for art in arts: | ||
| 84 | 88 | ||||