[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 | 53 | def fetch(self) -> list[Listing]: |
| 54 | 54 | listings: list[Listing] = [] |
| 55 | − # Hoquets réseau transitoires (vagues DNS du nœud 09-10/09-18, resets | |
| 56 | − # du VPS) : retry court, puis échec FRANC — un `return []` silencieux | |
| 57 | − # devenait « 0 trouvé ok » et déclenchait la dérive/stale à tort. | |
| 58 | − html = "" | |
| 55 | + # Hoquets transitoires récurrents : GET en échec (vagues DNS du nœud | |
| 56 | + # 09-10/09-18, resets du VPS) mais AUSSI 200 servi SANS cartes | |
| 57 | + # (épisodes 10-17 et 10-29) — les deux se retryent, puis échec FRANC | |
| 58 | + # (jamais de `return []` silencieux → « 0 trouvé ok » à tort). | |
| 59 | + arts = [] | |
| 59 | 60 | last_err: Exception | None = None |
| 60 | 61 | for attempt in range(3): |
| 62 | + if attempt: | |
| 63 | + time.sleep(3 * attempt) | |
| 61 | 64 | try: |
| 62 | 65 | html = self.get(LIST_URL).text |
| 63 | − break | |
| 64 | 66 | except Exception as e: # noqa: BLE001 — retry puis erreur franche |
| 65 | 67 | last_err = e |
| 66 | − if attempt < 2: | |
| 67 | − time.sleep(3 * (attempt + 1)) | |
| 68 | + continue | |
| 69 | + soup = BeautifulSoup(html, "html.parser") | |
| 70 | + arts = soup.select("article.c--article-project") | |
| 71 | + if arts: | |
| 72 | + break | |
| 73 | + last_err = None | |
| 68 | 74 | else: |
| 69 | − raise RuntimeError( | |
| 70 | − f"sotramont : portfolio inaccessible après 3 tentatives " | |
| 71 | − f"({last_err!r}) — {LIST_URL}") | |
| 72 | − soup = BeautifulSoup(html, "html.parser") | |
| 73 | − | |
| 74 | − arts = soup.select("article.c--article-project") | |
| 75 | − if not arts: | |
| 75 | + if last_err is not None: | |
| 76 | + raise RuntimeError( | |
| 77 | + f"sotramont : portfolio inaccessible après 3 tentatives " | |
| 78 | + f"({last_err!r}) — {LIST_URL}") | |
| 76 | 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 | 81 | raise RuntimeError( |
| 79 | − "sotramont : aucune carte projet dans le portfolio — " | |
| 80 | − f"site remanié ? — {LIST_URL}") | |
| 82 | + "sotramont : aucune carte projet dans le portfolio " | |
| 83 | + "(3 tentatives) — site remanié ? — " | |
| 84 | + f"{LIST_URL}") | |
| 81 | 85 | |
| 82 | 86 | seen: set[str] = set() |
| 83 | 87 | for art in arts: |
| 84 | 88 | |